USACO Better

USACO 优化插件

  1. // ==UserScript==
  2. // @name USACO Better
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.7
  5. // @description USACO 优化插件
  6. // @author ZnPdCo
  7. // @match https://usaco.org/*
  8. // @icon https://usaco.guide/favicon-32x32.png
  9. // @grant unsafeWindow
  10. // @connect www2.deepl.com
  11. // @connect www.iflyrec.com
  12. // @connect m.youdao.com
  13. // @connect api.interpreter.caiyunai.com
  14. // @connect translate.google.com
  15. // @connect greasyfork.org
  16. // @connect znpdco.github.io
  17. // @connect *
  18. // @grant GM_xmlhttpRequest
  19. // @grant GM_info
  20. // @grant GM_setValue
  21. // @grant GM_getValue
  22. // @grant GM_deleteValue
  23. // @grant GM_addStyle
  24. // @grant GM_setClipboard
  25. // @grant GM_registerMenuCommand
  26. // @require https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js
  27. // @license MIT
  28. // ==/UserScript==
  29.  
  30.  
  31.  
  32. (function() {
  33.  
  34. // 常量
  35. const translates = {
  36. 'gg': {'name': '谷歌翻译', 'func': translate_gg},
  37. 'youdao_mobile': {'name': '有道翻译', 'func': translate_youdao_mobile},
  38. 'deepl': {'name': 'deepl翻译', 'func': translate_deepl},
  39. 'iflyrec': {'name': '讯飞听见翻译', 'func': translate_iflyrec},
  40. };
  41. if(GM_getValue('translate') == undefined) {
  42. GM_setValue('translate', 'iflyrec');
  43. }
  44. if(GM_getValue('code_box') == undefined) {
  45. GM_setValue('code_box', true);
  46. }
  47.  
  48. // 设置页内容
  49. if(location.search == '?page=settings') {
  50. $(`
  51. <div class="panel-">
  52. <h2>USACO Better 设置</h2>
  53. </div>`).insertBefore(`.panel:eq(0)`)
  54. $(`.panel`).remove()
  55. $(`.panel-`).attr('class', 'panel')
  56.  
  57. // 题面翻译设置
  58. $('.panel').append(`翻译引擎设置:<select id="translate"></select><br><br>`)
  59. for (var [key, value] of Object.entries(translates)) {
  60. $('#translate').append(`<option value="${key}" ${GM_getValue('translate') == key ? 'selected' : ''}>${value['name']}</option>`);
  61. }
  62. $('#translate').change(function() {
  63. GM_setValue('translate', $('#translate').val());
  64. })
  65.  
  66.  
  67. // 提交代码框设置
  68. $('.panel').append(`提交代码时使用代码框:<input id="code_box" type="checkbox" ${GM_getValue('code_box') == true ? 'checked' : ''}><br><br>`)
  69. $('#code_box').change(function() {
  70. GM_setValue('code_box', $('#code_box').is(':checked'));
  71. })
  72.  
  73. // 更新翻译数据
  74. $('.panel').append(`更新翻译数据(频率:11次):<button id="update_translate"}>更新</button><br><br>`)
  75. $('#update_translate').click(function() {
  76. GM_setValue('translate_update_time', undefined);
  77. })
  78. }
  79.  
  80.  
  81. // 菜单栏展示规则页
  82. $('.navbar ul').append(`<li><a href="index.php?page=instructions">Instructions</a></li>`)
  83. // 菜单栏展示设置页
  84. $('.navbar ul').append(`<li><a href="index.php?page=settings">Settings</a></li>`)
  85. $('.navbar ul li a').css({'width': '79px'})
  86.  
  87. async function translate_by_rule() {
  88. if(GM_getValue('translate_update_time') == undefined || Date.now() - GM_getValue('translate_update_time') >= 1000 * 60 * 60 * 24) {
  89. var res = await Request({
  90. method: "GET",
  91. url: `https://znpdco.github.io/USACO-Better/translate.js`,
  92. })
  93. GM_setValue('translate_update_time', Date.now());
  94. GM_setValue('translate_rule', res.responseText);
  95. }
  96. eval(GM_getValue('translate_rule'));
  97. }
  98. translate_by_rule();
  99.  
  100. //--谷歌翻译--start
  101. async function translate_gg(raw) {
  102. return new Promise((resolve, reject) => {
  103. const url = 'https://translate.google.com/m';
  104. const params = `tl=zh-CN&q=${encodeURIComponent(raw)}`;
  105.  
  106. GM_xmlhttpRequest({
  107. method: 'GET',
  108. url: `${url}?${params}`,
  109. onload: function (response) {
  110. const html = response.responseText;
  111. const translatedText = $(html).find('.result-container').text();
  112. resolve(translatedText);
  113. },
  114. onerror: function (response) {
  115. reject("发生了未知的错误,请确认你是否能正常访问Google翻译,\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/471106/feedback 反馈 请注意打码报错信息的敏感部分\n\n响应报文:" + JSON.stringify(response))
  116. }
  117. });
  118. });
  119. }
  120. //--谷歌翻译--end
  121.  
  122. //--有道翻译m--start
  123. async function translate_youdao_mobile(raw) {
  124. const options = {
  125. method: "POST",
  126. url: 'http://m.youdao.com/translate',
  127. data: "inputtext=" + encodeURIComponent(raw) + "&type=AUTO",
  128. anonymous: true,
  129. headers: {
  130. "Content-Type": "application/x-www-form-urlencoded",
  131. 'Host': 'm.youdao.com',
  132. 'Origin': 'http://m.youdao.com',
  133. 'Referer': 'http://m.youdao.com/translate',
  134. }
  135. }
  136. return await BaseTranslate('有道翻译mobile', raw, options, res => /id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res)[1])
  137. }
  138. //--有道翻译m--end
  139.  
  140. //--Deepl翻译--start
  141. // 获得时间戳
  142. function getTimeStamp(iCount) {
  143. const ts = Date.now();
  144. if (iCount !== 0) {
  145. iCount = iCount + 1;
  146. return ts - (ts % iCount) + iCount;
  147. } else {
  148. return ts;
  149. }
  150. }
  151.  
  152. async function translate_deepl(raw) {
  153. const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
  154. const data = {
  155. jsonrpc: '2.0',
  156. method: 'LMT_handle_texts',
  157. id,
  158. params: {
  159. splitting: 'newlines',
  160. lang: {
  161. source_lang_user_selected: 'auto',
  162. target_lang: 'ZH',
  163. },
  164. texts: [{
  165. text: raw,
  166. requestAlternatives: 3
  167. }],
  168. timestamp: getTimeStamp(raw.split('i').length - 1)
  169. }
  170. }
  171. let postData = JSON.stringify(data);
  172. if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
  173. postData = postData.replace('"method":"', '"method" : "');
  174. } else {
  175. postData = postData.replace('"method":"', '"method": "');
  176. }
  177. const options = {
  178. method: 'POST',
  179. url: 'https://www2.deepl.com/jsonrpc',
  180. data: postData,
  181. headers: {
  182. 'Content-Type': 'application/json',
  183. 'Host': 'www2.deepl.com',
  184. 'Origin': 'https://www.deepl.com',
  185. 'Referer': 'https://www.deepl.com/',
  186. },
  187. anonymous: true,
  188. nocache: true,
  189. }
  190. return await BaseTranslate('Deepl翻译', raw, options, res => JSON.parse(res).result.texts[0].text)
  191. }
  192.  
  193. //--Deepl翻译--end
  194.  
  195. //--讯飞听见翻译--end
  196. async function translate_iflyrec(text) {
  197. const options = {
  198. method: "POST",
  199. url: 'https://www.iflyrec.com/TranslationService/v1/textTranslation',
  200. data: JSON.stringify({
  201. "from": "2",
  202. "to": "1",
  203. "contents": [{
  204. "text": text,
  205. "frontBlankLine": 0
  206. }]
  207. }),
  208. anonymous: true,
  209. headers: {
  210. 'Content-Type': 'application/json',
  211. 'Origin': 'https://www.iflyrec.com',
  212. },
  213. responseType: "json",
  214. };
  215. return await BaseTranslate('讯飞翻译', text, options, res => JSON.parse(res).biz[0].translateResult.replace(/\\n/g, "\n\n"));
  216. }
  217. //--讯飞听见翻译--end
  218.  
  219. //--异步请求包装工具--start
  220. async function PromiseRetryWrap(task, options, ...values) {
  221. const { RetryTimes, ErrProcesser } = options || {};
  222. let retryTimes = RetryTimes || 5;
  223. const usedErrProcesser = ErrProcesser || (err => { throw err });
  224. if (!task) return;
  225. while (true) {
  226. try {
  227. return await task(...values);
  228. } catch (err) {
  229. if (!--retryTimes) {
  230. console.warn(err);
  231. return usedErrProcesser(err);
  232. }
  233. }
  234. }
  235. }
  236.  
  237. async function BaseTranslate(name, raw, options, processer) {
  238. let errtext;
  239. const toDo = async () => {
  240. var tmp;
  241. try {
  242. const data = await Request(options);
  243. tmp = data.responseText;
  244. let result = await processer(tmp);
  245. return result;
  246. } catch (err) {
  247. errtext = tmp;
  248. throw {
  249. responseText: tmp,
  250. err: err
  251. }
  252. }
  253. }
  254. return await PromiseRetryWrap(toDo, { RetryTimes: 3, ErrProcesser: () => "翻译出错,请查看报错信息,并重试或更换翻译接口\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/490021/feedback 反馈 请注意打码报错信息的敏感部分\n\n报错信息:" + errtext })
  255. }
  256.  
  257.  
  258. function Request(options) {
  259. return new Promise((reslove, reject) => GM_xmlhttpRequest({ ...options, onload: reslove, onerror: reject }))
  260. }
  261.  
  262. async function show_translate_btn() {
  263. if($('#probtext-text').length) {
  264. $.ajax({
  265. type: "GET",
  266. url: location.href,
  267. async: false,
  268. success: function(data) {
  269. window.data = data
  270. },
  271. error: function(xhr, statusText, error) {}
  272. });
  273.  
  274. var origin_html = {};
  275. $('#probtext-text').html($(data).find('#probtext-text').html())
  276. var ele1 = $('#probtext-text').children()
  277. var ele2 = $('#probtext-text').contents().filter(function() {
  278. return this.nodeType === 3;
  279. })
  280. var ele = $.merge(ele1, ele2)
  281. for(let i = 0; i < ele.length; i++) {
  282. if(ele.eq(i).text().replaceAll('\n', '').replaceAll(' ', '') == '' || ele.get(i).tagName == 'PRE' || ele.eq(i).text().includes('SAMPLE INPUT:') || ele.eq(i).text().includes('SAMPLE OUTPUT:')) continue
  283. $(`
  284. <div style="text-align: right">
  285. <button style="margin-bottom:6px;
  286. background-color: transparent;
  287. color: #08c;
  288. margin-left: 4px;
  289. border: 1px solid #08c;
  290. border-radius: 3px;" class="fanyi" id="fanyi-${i}" type="button">翻译</button>
  291. </div>`).insertBefore(ele.eq(i))
  292. if(ele.get(i).nodeType == 3)origin_html[i] = ele.eq(i).text()
  293. else origin_html[i] = ele.eq(i).html()
  294. }
  295.  
  296. MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
  297.  
  298. $(`.fanyi`).click(async function(e) {
  299. var fanyi_id = parseInt($(e.target).attr("id").replace('fanyi-', ''));
  300. var text = origin_html[fanyi_id]
  301. var texts = text.split('$$')
  302. var res = ""
  303. var tex = {}
  304. var cnt = 0;
  305. for(let i = 0; i < texts.length; i++) {
  306. if(i % 2 == 0) res += texts[i];
  307. else {
  308. cnt++;
  309. tex[cnt] = '$$' + texts[i] + '$$';
  310. res += '{' + cnt + '}';
  311. }
  312. }
  313. text = res
  314. texts = text.split('$')
  315. res = ""
  316. for(let i = 0; i < texts.length; i++) {
  317. if(i % 2 == 0) res += texts[i];
  318. else {
  319. cnt++;
  320. tex[cnt] = '$' + texts[i] + '$';
  321. res += '{' + cnt + '}';
  322. }
  323. }
  324. text = res
  325.  
  326. if($(`#result-${fanyi_id}`).length == 0) {
  327. $(`<div align="left" id="result-${fanyi_id}" class="problem-text mathjax" style="width:750px; padding-top:10px;"></div>`).insertAfter(e.target)
  328. $(`
  329. <button style="margin-bottom:6px;
  330. background-color: transparent;
  331. color: #08c;
  332. margin-left: 4px;
  333. border: 1px solid #08c;
  334. border-radius: 3px;" type="button" onclick="
  335. function run(){
  336. if($('#result-${fanyi_id}').is(':hidden')) {
  337. $('#result-${fanyi_id}').show()
  338. } else {
  339. $('#result-${fanyi_id}').hide()
  340. }
  341. }
  342. run()">折叠、展开</button>
  343. `).insertBefore(e.target)
  344. }
  345. var timer = setInterval(function() {
  346. var tip = `正在使用 ${translates[GM_getValue('translate')]['name']} 翻译,稍安勿躁`;
  347. var tip1 = tip + '.';
  348. var tip2 = tip + '..';
  349. var tip3 = tip + '...';
  350. if($(`#result-${fanyi_id}`).html() == tip1) $(`#result-${fanyi_id}`).html(tip2);
  351. else if($(`#result-${fanyi_id}`).html() == tip2) $(`#result-${fanyi_id}`).html(tip3);
  352. else if($(`#result-${fanyi_id}`).html() == tip3) $(`#result-${fanyi_id}`).html(tip1);
  353. }, 100);
  354. $(`#result-${fanyi_id}`).html(`正在使用 ${translates[GM_getValue('translate')]['name']} 翻译,稍安勿躁.`);
  355.  
  356.  
  357. text = await translates[GM_getValue('translate')]['func'](text)
  358.  
  359. texts = text.split(/{|}/)
  360. res = ""
  361. for(let i = 0; i < texts.length; i++) {
  362. if(i % 2 == 0) res += texts[i]
  363. else res += tex[parseInt(texts[i])]
  364. }
  365. text = res
  366.  
  367. clearInterval(timer);
  368. $(`#result-${fanyi_id}`).html(text);
  369. $(e.target).text('重新翻译');
  370. MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
  371. })
  372. }
  373. }
  374. show_translate_btn();
  375. function show_code_editor() {
  376. if($('#probtext-text').length && GM_getValue('code_box') == true) {
  377. $('#solution .field2:eq(2)').remove()
  378. $('#solution .field2:eq(1)').remove()
  379. $(`
  380. <div class="field2">
  381. <label for="sourcefile">Your Code:</label>
  382. <div id="code" style="width: 800px; height: 500px;"></div></div>
  383. <div class="field2">
  384. <button id="solution-submit" type="button">Submit Solution</button></div>
  385. `).insertAfter('#solution .field2:eq(0)')
  386. $('#solution-submit').click(function() {
  387. var form = document.getElementsByClassName('submission')[0];
  388. var text = window.editor.getValue();
  389. var fileData = new File([text], 'foo.cpp', {
  390. type: 'multipart/form-data',
  391. });
  392. var formData = new FormData(form);
  393. formData.set('sourcefile', fileData)
  394. $.ajax({
  395. url: "current/tpcm/submit-solution.php",
  396. type: "POST",
  397. async: false,
  398. data: formData,
  399. processData: false, // 不处理数据
  400. contentType: false // 不设置内容类型
  401.  
  402. });
  403. location.reload();
  404. })
  405. $('#solution-submit').click(function() {
  406. var form = document.getElementsByClassName('submission')[0];
  407. var text = window.editor.getValue();
  408. var fileData = new File([text], 'foo.cpp', {
  409. type: 'multipart/form-data',
  410. });
  411. var formData = new FormData(form);
  412. formData.set('sourcefile', fileData)
  413. $.ajax({
  414. url: "current/tpcm/submit-solution.php",
  415. type: "POST",
  416. async: false,
  417. data: formData,
  418. processData: false, // 不处理数据
  419. contentType: false // 不设置内容类型
  420.  
  421. });
  422. window.scrollTo(0,0);
  423. location.reload();
  424. })
  425. $('select[name="language"]').change(function() {
  426. if($('select[name="language"]').val() == 1) {
  427. monaco.editor.setModelLanguage(window.editor.getModel(), "c")
  428. }
  429. if($('select[name="language"]').val() == 6) {
  430. monaco.editor.setModelLanguage(window.editor.getModel(), "cpp")
  431. }
  432. if($('select[name="language"]').val() == 7) {
  433. monaco.editor.setModelLanguage(window.editor.getModel(), "cpp")
  434. }
  435. if($('select[name="language"]').val() == 9) {
  436. monaco.editor.setModelLanguage(window.editor.getModel(), "java")
  437. }
  438. if($('select[name="language"]').val() == 3) {
  439. monaco.editor.setModelLanguage(window.editor.getModel(), "python")
  440. }
  441. if($('select[name="language"]').val() == 4) {
  442. monaco.editor.setModelLanguage(window.editor.getModel(), "python")
  443. }
  444. })
  445. // 使用 Monaco Editor
  446. const script = document.createElement('script');
  447. script.src = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.27.0/min/vs/loader.js';
  448. document.head.appendChild(script);
  449. script.onload = function() {
  450. require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs' }});
  451. require(['vs/editor/editor.main'], function() {
  452. window.editor = monaco.editor.create(document.getElementById('code'), {
  453. value: '',
  454. automaticLayout: true, // 自动布局
  455. foldingStrategy: 'indentation', // 代码可分小段折叠
  456. autoClosingBrackets: 'always', // 是否自动添加结束括号(包括中括号) "always" | "languageDefined" | "beforeWhitespace" | "never"
  457. autoClosingDelete: 'always', // 是否自动删除结束括号(包括中括号) "always" | "never" | "auto"
  458. autoClosingQuotes: 'always', // 是否自动添加结束的单引号 双引号 "always" | "languageDefined" | "beforeWhitespace" | "never"
  459. autoIndent: 'None', // 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
  460. comments: {
  461. ignoreEmptyLines: true, // 插入行注释时忽略空行。默认为真。
  462. insertSpace: true // 在行注释标记之后和块注释标记内插入一个空格。默认为真。
  463. }, // 注释配置
  464. //
  465. cursorBlinking: 'Solid', // 光标动画样式
  466. cursorSmoothCaretAnimation: true, // 是否启用光标平滑插入动画 当你在快速输入文字的时候 光标是直接平滑的移动还是直接"闪现"到当前文字所处位置
  467. cursorSurroundingLines: 0, // 光标环绕行数 当文字输入超过屏幕时 可以看见右侧滚动条中光标所处位置是在滚动条中间还是顶部还是底部 即光标环绕行数 环绕行数越大 光标在滚动条中位置越居中
  468. cursorSurroundingLinesStyle: 'all', // "default" | "all" 光标环绕样式
  469. cursorWidth: 2, // <=25 光标宽度
  470. overviewRulerBorder: false, // 是否应围绕概览标尺绘制边框
  471. folding: true, // 是否启用代码折叠
  472. scrollBeyondLastLine: false, // 设置编辑器是否可以滚动到最后一行之后
  473. renderLineHighlight: 'all', // 当前行突出显示方式 "all" | "line" | "none" | "gutter"
  474. theme: 'vs-dark', // 官方自带三种主题vs, hc-black, or vs-dark
  475. automaticLayout: true,
  476. language: 'c',
  477. stickyScroll: {
  478. enabled: false,
  479. },
  480. });
  481. });
  482. }
  483. }
  484. if(location.search.includes('?sid=')) {
  485. $('select[name="language"]').change(function() {
  486. if($('select[name="language"]').val() == 1) {
  487. monaco.editor.setModelLanguage(window.editor.getModel(), "c")
  488. }
  489. if($('select[name="language"]').val() == 6) {
  490. monaco.editor.setModelLanguage(window.editor.getModel(), "cpp")
  491. }
  492. if($('select[name="language"]').val() == 7) {
  493. monaco.editor.setModelLanguage(window.editor.getModel(), "cpp")
  494. }
  495. if($('select[name="language"]').val() == 9) {
  496. monaco.editor.setModelLanguage(window.editor.getModel(), "java")
  497. }
  498. if($('select[name="language"]').val() == 3) {
  499. monaco.editor.setModelLanguage(window.editor.getModel(), "python")
  500. }
  501. if($('select[name="language"]').val() == 4) {
  502. monaco.editor.setModelLanguage(window.editor.getModel(), "python")
  503. }
  504. })
  505. $(`<div id="code" style="width: 800px; height: 500px;"></div>`).insertBefore('pre');
  506. $('pre').hide();
  507. // 使用 Monaco Editor
  508. const script = document.createElement('script');
  509. script.src = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.27.0/min/vs/loader.js';
  510. document.head.appendChild(script);
  511. script.onload = function() {
  512. require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs' }});
  513. require(['vs/editor/editor.main'], function() {
  514. window.editor = monaco.editor.create(document.getElementById('code'), {
  515. value: $('pre').text(),
  516. automaticLayout: true, // 自动布局
  517. foldingStrategy: 'indentation', // 代码可分小段折叠
  518. autoClosingBrackets: 'always', // 是否自动添加结束括号(包括中括号) "always" | "languageDefined" | "beforeWhitespace" | "never"
  519. autoClosingDelete: 'always', // 是否自动删除结束括号(包括中括号) "always" | "never" | "auto"
  520. autoClosingQuotes: 'always', // 是否自动添加结束的单引号 双引号 "always" | "languageDefined" | "beforeWhitespace" | "never"
  521. autoIndent: 'None', // 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
  522. comments: {
  523. ignoreEmptyLines: true, // 插入行注释时忽略空行。默认为真。
  524. insertSpace: true // 在行注释标记之后和块注释标记内插入一个空格。默认为真。
  525. }, // 注释配置
  526. //
  527. cursorBlinking: 'Solid', // 光标动画样式
  528. cursorSmoothCaretAnimation: true, // 是否启用光标平滑插入动画 当你在快速输入文字的时候 光标是直接平滑的移动还是直接"闪现"到当前文字所处位置
  529. cursorSurroundingLines: 0, // 光标环绕行数 当文字输入超过屏幕时 可以看见右侧滚动条中光标所处位置是在滚动条中间还是顶部还是底部 即光标环绕行数 环绕行数越大 光标在滚动条中位置越居中
  530. cursorSurroundingLinesStyle: 'all', // "default" | "all" 光标环绕样式
  531. cursorWidth: 2, // <=25 光标宽度
  532. overviewRulerBorder: false, // 是否应围绕概览标尺绘制边框
  533. folding: true, // 是否启用代码折叠
  534. scrollBeyondLastLine: false, // 设置编辑器是否可以滚动到最后一行之后
  535. renderLineHighlight: 'all', // 当前行突出显示方式 "all" | "line" | "none" | "gutter"
  536. theme: 'vs-dark', // 官方自带三种主题vs, hc-black, or vs-dark
  537. automaticLayout: true,
  538. readOnly: true,
  539. language: 'cpp',
  540. stickyScroll: {
  541. enabled: false,
  542. },
  543. });
  544. });
  545. }
  546. }
  547. }
  548. show_code_editor();
  549. })();