Codeforces Better!

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

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

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