Codeforces Better!

Codeforces界面汉化、题目翻译,markdown视图,一键复制题目

当前为 2023-05-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Codeforces Better!
  3. // @namespace https://greasyfork.org/users/747162
  4. // @version 1.02
  5. // @description Codeforces界面汉化、题目翻译,markdown视图,一键复制题目
  6. // @author 北极小狐
  7. // @match https://codeforces.com/*
  8. // @connect m.youdao.com
  9. // @connect www2.deepl.com
  10. // @grant GM_xmlhttpRequest
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=codeforces.com
  12. // @require https://cdn.bootcdn.net/ajax/libs/turndown/7.1.1/turndown.min.js
  13. // @license MIT
  14. // ==/UserScript==
  15. // 样式
  16. function loadCssCode(code){
  17. var style = document.createElement('style');
  18. style.type = 'text/css';
  19. style.rel = 'stylesheet';
  20. style.appendChild(document.createTextNode(code));
  21. var head = document.getElementsByTagName('head')[0];
  22. head.appendChild(style);
  23. }
  24. loadCssCode(`
  25. span.mdViewContent {
  26. white-space: pre-wrap;
  27. }
  28. .translate-problem-statement {
  29. white-space: pre-wrap;
  30. border: 1px dashed #00aeeccc;
  31. border-radius: 0.3rem;
  32. padding: 5px;
  33. margin: 5px 0px;
  34. }
  35. .html2md-panel {
  36. display: flex;
  37. justify-content: flex-end;
  38. }
  39. button.html2mdButton {
  40. height: 3vh;
  41. width: 3vh;
  42. }
  43. button.html2mdButton {
  44. cursor: pointer;
  45. background-color: #e6e6e6;
  46. color: #727378;
  47. height: 3vh;
  48. width: auto;
  49. font-size: 1.3vh;
  50. border-radius: 0.3rem;
  51. border: none;
  52. padding: 1px 5px;
  53. margin: 5px;
  54. box-shadow: 0 0 1px #0000004d;
  55. }
  56. button.html2mdButton.copied {
  57. background-color: #07e65196;
  58. color: #104f2b;
  59. }
  60. button.html2mdButton.html2md-view.mdViewed {
  61. background-color: #ff980057;
  62. color: #5a3a0c;
  63. }
  64. button.translated {
  65. cursor: not-allowed;
  66. background-color: #2a6dc296;
  67. color: #fffdfd;
  68. }
  69. `);
  70. // 汉化替换
  71. (function() {
  72. // 设置语言为zh
  73. var htmlTag = document.getElementsByTagName("html")[0];
  74. htmlTag.setAttribute("lang", "zh-CN");
  75.  
  76. // 定义classData,存放元素的class名和对应的替换规则
  77. const classData = {
  78. '.menu-list.main-menu-list': [
  79. { match: 'Help', replace: '帮助' },
  80. { match: 'Calendar', replace: '日历' },
  81. { match: 'Edu', replace: '培训' },
  82. { match: 'Rating', replace: '积分榜' },
  83. { match: 'Groups', replace: '团体' },
  84. { match: 'Problemset', replace: '题单' },
  85. { match: 'Gym', replace: '训练营(过去的大型比赛)' },
  86. { match: 'Contests', replace: '比赛' },
  87. { match: 'Catalog', replace: '指南目录' },
  88. { match: 'Top', replace: '热门' },
  89. { match: 'Home', replace: '主页' },
  90. ],
  91. '.caption.titled': [
  92. { match: 'Pay attention', replace: '注意' },
  93. { match: 'Top rated', replace: '积分排行' },
  94. { match: 'Top contributors', replace: '贡献者排行' },
  95. { match: 'Find user', replace: '查找用户' },
  96. { match: 'Recent actions', replace: '最近热帖' },
  97. { match: 'Training filter', replace: '过滤筛选' },
  98. { match: 'Find training', replace: '搜索比赛/问题' },
  99. { match: 'Virtual participation', replace: '什么是虚拟参赛' },
  100. { match: 'Contest materials', replace: '比赛相关资料' },
  101. { match: 'Settings', replace: '设置' },
  102. { match: 'Clone Contest to Mashup', replace: 'Clone比赛到组合混搭' },
  103. { match: 'Submit', replace: '提交' },
  104. ],
  105. '.personal-sidebar ': [
  106. { match: 'Contribution', replace: '贡献' },
  107. { match: 'Settings', replace: '设置' },
  108. { match: 'Blog', replace: '博客' },
  109. { match: 'Teams', replace: '队伍' },
  110. { match: 'Submissions', replace: '提交' },
  111. { match: 'Talks', replace: '私信' },
  112. { match: 'Contests', replace: '比赛' },
  113. ],
  114. '.contest-state-phase': [
  115. { match: 'Before contest', replace: '即将进行的比赛' },
  116. ],
  117. '.act-item': [
  118. { match: 'Add to favourites', replace: '添加到收藏' },
  119. { match: 'Submit', replace: '提交' },
  120. ],
  121. '.datatable': [
  122. { match: 'Virtual participation', replace: '参加虚拟重现赛' },
  123. { match: 'Enter', replace: '进入' },
  124. { match: 'Final standings', replace: '榜单' },
  125. { match: 'School/University/City/Region Championship', replace: '学校/大学/城市/区域比赛' },
  126. { match: 'Official School Contest', replace: '官方学校比赛' },
  127. { match: 'Training Contest', replace: '训练赛' },
  128. { match: 'Training Camp Contest', replace: '训练营比赛' },
  129. { match: 'Official ICPC Contest', replace: 'ICPC官方比赛' },
  130. { match: 'Official International Personal Contest', replace: '官方国际个人赛' },
  131. { match: 'China', replace: '中国' },
  132. { match: 'Statements', replace: '题目描述' },
  133. { match: 'in Chinese', replace: '中文' },
  134. { match: 'Trainings', replace: '训练' },
  135. { match: 'Prepared by', replace: '编写人' },
  136. { match: 'Current or upcoming contests', replace: '当前或即将举行的比赛' },
  137. { match: 'Past contests', replace: '过去的比赛' },
  138. { match: 'Exclusions', replace: '排除' },
  139. { match: 'Before start', replace: '还有' },
  140. { match: 'Before registration', replace: '报名还有' },
  141. { match: 'Until closing ', replace: '结束报名' },
  142. { match: 'Register', replace: '报名' },
  143. { match: 'Registration completed', replace: '已报名' },
  144. { match: 'Questions about problems', replace: '关于题目的提问' },
  145. ],
  146. '.ask-question-link': [
  147. { match: 'Ask a question', replace: '提一个问题' },
  148. ],
  149. '.contests-table': [
  150. { match: 'Contest history', replace: '比赛历史' },
  151. ],
  152. '.roundbox.sidebox.borderTopRound ': [
  153. { match: 'Season:', replace: '时间范围(年度)' },
  154. { match: 'Contest type', replace: '比赛类型' },
  155. { match: 'ICPC region', replace: 'ICPC地区' },
  156. { match: 'Contest format', replace: '比赛形式' },
  157. { match: 'Duration, hours', replace: '持续时间(小时)' },
  158. { match: 'Order by', replace: '排序方式' },
  159. { match: 'Secondary order by', replace: '次要排序方式' },
  160. { match: 'Hide, if participated', replace: '隐藏我参与过的' },
  161. { match: 'Hide excluded gyms', replace: '隐藏排除的比赛' },
  162. { match: 'Register now', replace: '现在报名' },
  163. { match: 'Show tags for unsolved problems', replace: '显示未解决问题的标签' },
  164. { match: 'Hide solved problems', replace: '隐藏已解决的问题' },
  165. ],
  166. '.icon-eye-close.icon-large': [
  167. { match: 'Add to exclusions', replace: '添加到排除列表' },
  168. ],
  169. '._ContestFilterExclusionsManageFrame_addExclusionLink': [
  170. { match: 'Add to exclusions for gym contests filter', replace: '添加训练营过滤器的排除项' },
  171. ],
  172. '.roundbox.sidebox.sidebar-menu.borderTopRound ': [
  173. { match: 'Announcement', replace: '公告' },
  174. { match: 'Statements', replace: '统计报表' },
  175. { match: 'Tutorial', replace: '题解' },
  176. ],
  177. '.second-level-menu ': [
  178. { match: 'Problems', replace: '问题' },
  179. { match: 'Submit Code', replace: '提交代码' },
  180. { match: 'My Submissions', replace: '我的提交' },
  181. { match: 'Status', replace: '状态' },
  182. { match: 'Standings', replace: '榜单' },
  183. { match: 'Custom Invocation', replace: '自定义调试' },
  184. { match: 'Common standings', replace: '全部排行' },
  185. { match: 'Friends standings', replace: '只看朋友' },
  186. { match: 'Submit', replace: '提交' },
  187. { match: 'Custom test', replace: '自定义测试' },
  188. { match: 'Blog', replace: '博客' },
  189. { match: 'Teams', replace: '队伍' },
  190. { match: 'Submissions', replace: '提交' },
  191. { match: 'Groups', replace: '团体' },
  192. { match: 'Contests', replace: '比赛' },
  193. { match: '问题etting', replace: '参与编写的问题' },
  194. { match: 'Gym', replace: '训练营' },
  195. { match: 'Mashups', replace: '组合混搭' },
  196. { match: 'Posts', replace: '帖子' },
  197. { match: 'Comments', replace: '回复' },
  198. { match: 'Main', replace: '主要' },
  199. { match: 'Settings', replace: '设置' },
  200. { match: 'Lists', replace: '列表' },
  201. ],
  202. '.topic-toggle-collapse': [
  203. { match: 'Expand', replace: '展开' },
  204. ],
  205. '.topic-read-more': [
  206. { match: 'Full text and comments', replace: '阅读全文/评论' },
  207. ],
  208. '.toggleEditorCheckboxLabel': [
  209. { match: 'Switch off editor', replace: '关闭编辑器语法高亮' },
  210. ],
  211. '.content-with-sidebar': [
  212. { match: 'Notice', replace: '注意' },
  213. { match: 'virtual participation', replace: '虚拟参与' },
  214. { match: 'Registration for the contest', replace: '比赛报名' },
  215. { match: 'Terms of agreement', replace: '协议条款' },
  216. { match: 'Take part', replace: '参与' },
  217. { match: 'as individual participant', replace: '作为个人参与者' },
  218. { match: 'as a team member', replace: '作为团队成员' },
  219. { match: 'Virtual start time', replace: '虚拟开始时间' },
  220. { match: 'Complete problemset', replace: '完整的问题集' },
  221. ],
  222. '.submit': [
  223. { match: 'Registration for the contest', replace: '比赛报名' },
  224. ],
  225. '.table-form': [
  226. { match: 'Problem', replace: '题目' },
  227. { match: 'Language', replace: '语言' },
  228. { match: 'Source code', replace: '源代码' },
  229. { match: 'Or choose file', replace: '或者选择文件' },
  230. { match: 'Choose file', replace: '选择文件' },
  231. ],
  232. };
  233.  
  234. // 将所有 class 名与之符合的元素的 text 中包含的匹配的文字均替换为对应的文字
  235. for (const className in classData) {
  236. const elements = document.querySelectorAll(className);
  237. elements.forEach((element) => {
  238. let html = element.outerHTML; // 获取元素的 html 代码
  239. const parent = element.parentNode;
  240. const childIndex = Array.from(parent.children).indexOf(element);
  241. let matched = false; // 标记该元素是否匹配了规则
  242. classData[className].forEach((rule) => {
  243. if (html.match(new RegExp(rule.match, 'g'))) {
  244. // 如果匹配成功,则将 matched 设置为 true
  245. matched = true;
  246. html = html.replace(new RegExp(rule.match, 'g'), rule.replace); // 将其中匹配的文字替换为对应的文字
  247. const temp = document.createElement('div'); // 创建临时元素
  248. temp.innerHTML = html.trim();
  249. const newElement = element.cloneNode(true);
  250. newElement.innerHTML = temp.firstChild.innerHTML;
  251. parent.replaceChild(newElement, parent.children[childIndex]); // 替换元素
  252. }
  253. });
  254. });
  255. }
  256. })();
  257.  
  258. // 题目翻译
  259. window.onload = function() {
  260. let turndownService = new TurndownService();
  261.  
  262. turndownService.keep(['del']);
  263.  
  264. // **处理规则**
  265. // 忽略sample-tests
  266. turndownService.addRule('ignore-sample-tests', {
  267. filter: function(node) {
  268. return node.classList.contains('sample-tests')|| node.classList.contains('header');
  269. },
  270. replacement: function (content, node) {
  271. return "";
  272. }
  273. });
  274.  
  275. // remove <script> math
  276. turndownService.addRule('remove-script', {
  277. filter: function (node, options) {
  278. return node.tagName.toLowerCase() == "script" && node.type.startsWith("math/tex");
  279. },
  280. replacement: function (content, node) {
  281. return "";
  282. }
  283. });
  284.  
  285. // inline math
  286. turndownService.addRule('inline-math', {
  287. filter: function (node, options) {
  288. return node.tagName.toLowerCase() == "span" && node.className == "MathJax";
  289. },
  290. replacement: function (content, node) {
  291. return "$ " + $(node).next().text() + " $";
  292. }
  293. });
  294.  
  295. // block math
  296. turndownService.addRule('block-math', {
  297. filter: function (node, options) {
  298. return node.tagName.toLowerCase() == "div" && node.className == "MathJax_Display";
  299. },
  300. replacement: function (content, node) {
  301. return "\n$$\n" + $(node).next().text() + "\n$$\n";
  302. }
  303. });
  304.  
  305. // **题干**
  306. // 添加按钮
  307. $("div[class='problem-statement']").each(function() {
  308. $(this).children(':eq(1)').before(
  309. "<div class='html2md-panel'> <button class='html2mdButton html2md-view1'>MarkDown视图</button> <button class='html2mdButton html2md-cb1'>Copy</button> <button class='html2mdButton translateButton1'>翻译</button> </div>"
  310. );
  311. });
  312.  
  313.  
  314. $(".html2md-cb1").click(function() {
  315. let target = $(this).parent().next().get(0);
  316. if (!target.markdown){
  317. target.markdown = turndownService.turndown($(target).html());
  318. }
  319. const textarea = document.createElement('textarea');
  320. textarea.value = target.markdown;
  321. document.body.appendChild(textarea);
  322. textarea.select();
  323. document.execCommand('copy');
  324. document.body.removeChild(textarea);
  325. $(this).addClass("copied");
  326. $(this).text("Copied");
  327. // 更新复制按钮文本
  328. setTimeout(() => {
  329. $(this).removeClass("copied");
  330. $(this).text("Copy");
  331. }, 2000);
  332. });
  333.  
  334. $(".html2md-view1").click(function() {
  335. let target = $(this).parent().next().get(0);
  336. if (target.viewmd) {
  337. target.viewmd = false;
  338. $(this).text("MarkDown视图");
  339. $(this).removeClass("mdViewed");
  340. $(target).html(target.original_html);
  341. } else {
  342. target.viewmd = true;
  343. if (!target.original_html)
  344. target.original_html = $(target).html();
  345. if (!target.markdown)
  346. target.markdown = turndownService.turndown($(target).html());
  347. $(this).text("原始内容");
  348. $(this).addClass("mdViewed");
  349. $(target).html(`<span class="mdViewContent" oninput="$(this).parent().get(0).markdown=this.value;" style="width:auto; height:auto;"> ${target.markdown} </span>`);
  350. }
  351. });
  352. $(".translateButton1").click(function() {
  353. let target = $(this).parent().next().get(0);
  354. if (!target.markdown){
  355. target.markdown = turndownService.turndown($(target).html());
  356. }
  357. const textarea = document.createElement('textarea');
  358. textarea.value = target.markdown;
  359. // 翻译处理
  360. var element_node = $("div.problem-statement").children(':eq(2)').get(0);
  361. translateProblemStatement(textarea.value, element_node);
  362. //
  363. $(this).addClass("translated");
  364. $(this).text("已翻译");
  365. $(this).prop("disabled",true);
  366. });
  367. // **Input**
  368. // 添加按钮
  369. $("div[class='input-specification']").each(function() {
  370. $(this).children(':eq(1)').before(
  371. "<div class='html2md-panel'> <button class='html2mdButton html2md-view2'>MarkDown视图</button> <button class='html2mdButton html2md-cb2'>Copy</button> <button class='html2mdButton translateButton2'>翻译</button> </div>"
  372. );
  373. });
  374.  
  375.  
  376. $(".html2md-cb2").click(function() {
  377. let RelTarget = $(this).parent().parent().get(0);
  378. let target = $(RelTarget).clone(); // 创建副本
  379. $(target).children(':first').remove(); // 删除前两个元素(标题和按钮面板)
  380. $(target).children(':first').remove();
  381. if (!target.markdown){
  382. target.markdown = turndownService.turndown($(target).html());
  383. }
  384. const textarea = document.createElement('textarea');
  385. textarea.value = target.markdown;
  386. document.body.appendChild(textarea);
  387. textarea.select();
  388. document.execCommand('copy');
  389. document.body.removeChild(textarea);
  390. $(this).addClass("copied");
  391. $(this).text("Copied");
  392. // 更新复制按钮文本
  393. setTimeout(() => {
  394. $(this).removeClass("copied");
  395. $(this).text("Copy");
  396. }, 2000);
  397. $(target).remove();
  398. });
  399.  
  400. $(".html2md-view2").click(function() {
  401. let target = $(this).parent().parent().get(0);
  402. // 临时删除前两个子元素(标题和按钮面板)
  403. var removedChildren = $(this).parent().parent().children().eq(0).add($(this).parent().parent().children().eq(1)).detach();
  404. //
  405. if (target.viewmd) {
  406. target.viewmd = false;
  407. $(this).text("MarkDown视图");
  408. $(this).removeClass("mdViewed");
  409. $(target).html(target.original_html);
  410. } else {
  411. target.viewmd = true;
  412. if (!target.original_html)
  413. target.original_html = $(target).html();
  414. if (!target.markdown)
  415. target.markdown = turndownService.turndown($(target).html());
  416. $(this).text("原始内容");
  417. $(this).addClass("mdViewed");
  418. $(target).html(`<span class="mdViewContent oninput="$(this).parent().get(0).markdown=this.value;" style="width:auto; height:auto;"> ${target.markdown} </span>`);
  419. }
  420. // 恢复删除的元素
  421. $(".input-specification").prepend(removedChildren);
  422. });
  423. $(".translateButton2").click(function() {
  424. let RelTarget = $(this).parent().parent().get(0);
  425. let target = $(RelTarget).clone(); // 创建副本
  426. $(target).children(':first').remove(); // 删除前两个元素(标题和按钮面板)
  427. $(target).children(':first').remove();
  428. if (!target.markdown){
  429. target.markdown = turndownService.turndown($(target).html());
  430. }
  431. const textarea = document.createElement('textarea');
  432. textarea.value = target.markdown;
  433. // 翻译处理
  434. var element_node = $("div.input-specification").get(0);
  435. translateProblemStatement(textarea.value, element_node);
  436. //
  437. $(this).addClass("translated");
  438. $(this).text("已翻译");
  439. $(this).prop("disabled",true);
  440. $(target).remove();
  441. });
  442. // **Output**
  443. // 添加按钮
  444. $("div[class='output-specification']").each(function() {
  445. $(this).children(':eq(1)').before(
  446. "<div class='html2md-panel'> <button class='html2mdButton html2md-view3'>MarkDown视图</button> <button class='html2mdButton html2md-cb3'>Copy</button> <button class='html2mdButton translateButton3'>翻译</button> </div>"
  447. );
  448. });
  449.  
  450.  
  451. $(".html2md-cb3").click(function() {
  452. let RelTarget = $(this).parent().parent().get(0);
  453. let target = $(RelTarget).clone(); // 创建副本
  454. $(target).children(':first').remove(); // 删除前两个元素(标题和按钮面板)
  455. $(target).children(':first').remove();
  456. if (!target.markdown){
  457. target.markdown = turndownService.turndown($(target).html());
  458. }
  459. const textarea = document.createElement('textarea');
  460. textarea.value = target.markdown;
  461. document.body.appendChild(textarea);
  462. textarea.select();
  463. document.execCommand('copy');
  464. document.body.removeChild(textarea);
  465. $(this).addClass("copied");
  466. $(this).text("Copied");
  467. // 更新复制按钮文本
  468. setTimeout(() => {
  469. $(this).removeClass("copied");
  470. $(this).text("Copy");
  471. }, 2000);
  472. $(target).remove();
  473. });
  474.  
  475. $(".html2md-view3").click(function() {
  476. let target = $(this).parent().parent().get(0);
  477. // 临时删除前两个子元素(标题和按钮面板)
  478. var removedChildren = $(this).parent().parent().children().eq(0).add($(this).parent().parent().children().eq(1)).detach();
  479. //
  480. if (target.viewmd) {
  481. target.viewmd = false;
  482. $(this).text("MarkDown视图");
  483. $(this).removeClass("mdViewed");
  484. $(target).html(target.original_html);
  485. } else {
  486. target.viewmd = true;
  487. if (!target.original_html)
  488. target.original_html = $(target).html();
  489. if (!target.markdown)
  490. target.markdown = turndownService.turndown($(target).html());
  491. $(this).text("原始内容");
  492. $(this).addClass("mdViewed");
  493. $(target).html(`<span class="mdViewContent" oninput="$(this).parent().get(0).markdown=this.value;" style="width:auto; height:auto;"> ${target.markdown} </span>`);
  494. }
  495. // 恢复删除的元素
  496. $(".output-specification").prepend(removedChildren);
  497. });
  498. $(".translateButton3").click(function() {
  499. let RelTarget = $(this).parent().parent().get(0);
  500. let target = $(RelTarget).clone(); // 创建副本
  501. $(target).children(':first').remove(); // 删除前两个元素(标题和按钮面板)
  502. $(target).children(':first').remove();
  503. if (!target.markdown){
  504. target.markdown = turndownService.turndown($(target).html());
  505. }
  506. const textarea = document.createElement('textarea');
  507. textarea.value = target.markdown;
  508. // 翻译处理
  509. var element_node = $("div.output-specification").get(0);
  510. translateProblemStatement(textarea.value, element_node);
  511. //
  512. $(this).addClass("translated");
  513. $(this).text("已翻译");
  514. $(this).prop("disabled",true);
  515. $(target).remove();
  516. });
  517. };
  518.  
  519.  
  520. // 翻译处理
  521. async function translateProblemStatement(text, element_node){
  522. let id = Math.floor(Date.now() / 1000);
  523. // 替换latex公式
  524. let matches = text.match(/\$(.*?)\$/g);
  525. let replacements = {};
  526. try{
  527. for (let i = 0; i < matches.length; i++) {
  528. let match = matches[i];
  529. text = text.replace(match, `【${i + 1}】`);
  530. replacements[`【${i + 1}】`] = match;
  531. }
  532. }catch(e){}
  533. text = text.replace(/\\/g, "");
  534. // 翻译
  535. let translatedText = await translate_youdao_mobile(text);
  536. // 还原latex公式
  537. try{
  538. for (let i = 0; i < matches.length; i++) {
  539. let match = matches[i];
  540. let replacement = replacements[`【${i + 1}】`];
  541. translatedText = translatedText.replace(`【${i + 1}】`, replacement);
  542. }
  543. }catch(e){}
  544. translatedText = translatedText.replace(/\$/g, "$$$$$$"); // 使符合mathjx的转换语法
  545. const translateDiv = document.createElement('div');
  546. // 创建元素并放在element_node的后面
  547. translateDiv.setAttribute('id', id);
  548. translateDiv.classList.add('translate-problem-statement');
  549. const spanElement = document.createElement('span');
  550. translateDiv.textContent = translatedText;
  551. translateDiv.appendChild(spanElement);
  552. element_node.insertAdjacentElement('afterend', translateDiv);
  553. // 渲染Latex
  554. MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.getElementById(id)]);
  555. }
  556.  
  557. //--有道翻译m--start
  558. async function translate_youdao_mobile(raw){
  559. const options = {
  560. method:"POST",
  561. url:'http://m.youdao.com/translate',
  562. data:"inputtext="+encodeURIComponent(raw)+"&type=AUTO",
  563. anonymous:true,
  564. headers: {
  565. "Content-Type": "application/x-www-form-urlencoded"
  566. }
  567. }
  568. return await BaseTranslate('有道翻译mobile',raw,options,res=>/id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res)[1])
  569. }
  570. //--有道翻译m--end
  571.  
  572. //--Deepl翻译--start
  573.  
  574. function getTimeStamp(iCount) {
  575. const ts = Date.now();
  576. if (iCount !== 0) {
  577. iCount = iCount + 1;
  578. return ts - (ts % iCount) + iCount;
  579. } else {
  580. return ts;
  581. }
  582. }
  583.  
  584. async function translate_deepl(raw) {
  585. const id = (Math.floor(Math.random() * 99999) + 100000)* 1000;
  586. const data = {
  587. jsonrpc: '2.0',
  588. method: 'LMT_handle_texts',
  589. id,
  590. params: {
  591. splitting: 'newlines',
  592. lang: {
  593. source_lang_user_selected: 'auto',
  594. target_lang: 'ZH',
  595. },
  596. texts: [{
  597. text: raw,
  598. requestAlternatives:3
  599. }],
  600. timestamp: getTimeStamp(raw.split('i').length - 1)
  601. }
  602. }
  603. let postData = JSON.stringify(data);
  604. if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
  605. postData = postData.replace('"method":"', '"method" : "');
  606. } else {
  607. postData = postData.replace('"method":"', '"method": "');
  608. }
  609. const options = {
  610. method: 'POST',
  611. url: 'https://www2.deepl.com/jsonrpc',
  612. data: postData,
  613. headers: {
  614. 'Content-Type': 'application/json',
  615. 'Host': 'www.deepl.com',
  616. 'Origin': 'https://www.deepl.com',
  617. 'Referer': 'https://www.deepl.com/'
  618. },
  619. anonymous:true,
  620. nocache:true,
  621. }
  622. return await BaseTranslate('Deepl翻译',raw,options,res=>JSON.parse(res).result.texts[0].text)
  623. }
  624.  
  625. //--Deepl翻译--end
  626.  
  627. //--异步请求包装工具--start
  628.  
  629. async function PromiseRetryWrap(task,options,...values){
  630. const {RetryTimes,ErrProcesser} = options||{};
  631. let retryTimes = RetryTimes||5;
  632. const usedErrProcesser = ErrProcesser || (err =>{throw err});
  633. if(!task)return;
  634. while(true){
  635. try{
  636. return await task(...values);
  637. }catch(err){
  638. if(!--retryTimes){
  639. console.log(err);
  640. return usedErrProcesser(err);
  641. }
  642. }
  643. }
  644. }
  645.  
  646. async function BaseTranslate(name,raw,options,processer){
  647. const toDo = async ()=>{
  648. var tmp;
  649. try{
  650. const data = await Request(options);
  651. tmp = data.responseText;
  652. const result = await processer(tmp);
  653. if(result)sessionStorage.setItem(name+'-'+raw,result);
  654. return result
  655. }catch(err){
  656. throw {
  657. responseText: tmp,
  658. err: err
  659. }
  660. }
  661. }
  662. return await PromiseRetryWrap(toDo,{RetryTimes:3,ErrProcesser:()=>"翻译出错"})
  663. }
  664.  
  665. function Request(options){
  666. return new Promise((reslove,reject)=>GM_xmlhttpRequest({...options,onload:reslove,onerror:reject}))
  667. }
  668.  
  669. //--异步请求包装工具--end