CFCodereviewer

Codereview codeforces

目前為 2024-11-13 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name CFCodereviewer
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1.2
  5. // @description Codereview codeforces
  6. // @author kdzestelov
  7. // @license MIT
  8. // @match *://*codeforces.com/*
  9. // @grant GM_xmlhttpRequest
  10. // @updateUrl https://gist.githubusercontent.com/KhetagAb/000670a28c8a37b917ece858b142b747/raw/script.js
  11. // ==/UserScript==
  12.  
  13. const SHEET_URL = 'https://script.google.com/macros/s/AKfycbxGoJrsWBtwNFcYcXBNrk-PUTIOWrsRvPqDxBzBqT_TFpfaurt8IB5Dc9dEQU97-Eoyug/exec';
  14.  
  15. const SUB_RJ_BUTTON_CLASS = "submission-rj-button";
  16. const SUB_AC_BUTTON_CLASS = "submission-ac-button";
  17. const SUB_RG_BUTTON_CLASS = "submission-rejudge-form";
  18. const SUB_COMMENT_SEND_BUTTON_CLASS = "submission-comment-send-form";
  19.  
  20. function getLast(href) {
  21. return href.split("/").pop()
  22. }
  23.  
  24. function getGroupUrl() {
  25. const url = window.location.href
  26. return url.substring(0, url.indexOf("contest"))
  27. }
  28.  
  29. const getContestUrl = () => {
  30. const url = window.location.href
  31. return url.substring(0, url.indexOf("status"))
  32. }
  33.  
  34. function getContestId() {
  35. const url = window.location.href
  36. return url.substr(url.indexOf("contest") + 8, 6);
  37. }
  38.  
  39. function wrap(text) {
  40. return '[' + text + ']';
  41. }
  42.  
  43. function getSubRjButton(subId) {
  44. return $("[submissionid=" + subId + "] ." + SUB_RJ_BUTTON_CLASS);
  45. }
  46.  
  47. function getSubAcButton(subId) {
  48. return $("[submissionid=" + subId + "] ." + SUB_AC_BUTTON_CLASS);
  49. }
  50.  
  51. function getCommentSubAcButton(subId) {
  52. return $("." + SUB_COMMENT_SEND_BUTTON_CLASS)
  53. }
  54.  
  55. function getProblemIndex(subId) {
  56. return getLast($("tr[data-submission-id=" + subId + "] .status-small a").attr('href'));
  57. }
  58.  
  59. function getSubRow(subId) {
  60. return $('tr[data-submission-id="' + subId + '"]')
  61. }
  62.  
  63. function getHandle(subId) {
  64. return $("tr[data-submission-id=" + subId + "] .status-party-cell").text().trim();
  65. }
  66.  
  67. function getAllSubmissionsRow() {
  68. return $(".status-frame-datatable tbody tr")
  69. }
  70.  
  71. function getSubmissionRow(subId) {
  72. return $(".status-frame-datatable tbody tr[data-submission-id=" + subId + "]")
  73. }
  74.  
  75. function getSubsId() {
  76. return $(".information-box-link")
  77. }
  78.  
  79. function getCorrectSubs() {
  80. return $(".information-box-link .verdict-accepted").parent();
  81. }
  82.  
  83. function getSubButtons() {
  84. return $(".submission-action-form");
  85. }
  86.  
  87. function getSideBar() {
  88. return $("div[id=sidebar]")
  89. }
  90.  
  91. function getFilterBox() {
  92. return $(".status-filter-box");
  93. }
  94.  
  95. const getSheetSubmissions = () => {
  96. const fullUrl = SHEET_URL + "?type=get"
  97. GM_xmlhttpRequest({
  98. method: 'GET',
  99. url: fullUrl,
  100. onload: (response) => {
  101. if (response.status === 200) {
  102. try {
  103. console.log("Going to parse response: " + response.responseText);
  104. var submissions = JSON.parse(response.responseText)
  105. submissions = JSON.parse(response.responseText)
  106. localStorage.setItem("c_status", JSON.stringify(submissions));
  107. console.log("Submissions loaded from sheet: " + submissions);
  108. } catch (e) {
  109. console.error("Unable to parse submissions: " + e);
  110. }
  111. } else {
  112. console.error("Unable to load submissions from sheet: " + response.responseText);
  113. }
  114. },
  115. onerror: (error) => {
  116. console.error(error);
  117. }
  118. });
  119. }
  120.  
  121. const acceptSheetSubmission = (subId, button, type) => {
  122. const full_url = SHEET_URL + "?type=" + type + "&value=" + subId;
  123. GM_xmlhttpRequest({
  124. method: 'GET',
  125. url: full_url,
  126. timeout: 5000,
  127. onload: (response) => {
  128. if (response.status === 200) {
  129. const submissions = getSubmissions();
  130. submissions.push(subId);
  131. localStorage.setItem("c_status", JSON.stringify(submissions));
  132. button.style.backgroundColor = "#81D718";
  133. button.style.borderColor = "#81D718";
  134. if (showed_codes[subId] != null && showed_codes[subId].showed) {
  135. showed_codes[subId]["showButton"].click();
  136. }
  137. button.innerText = (type === "star" ? "🔥 Восхвалено" : "Похвалить");
  138. } else {
  139. button.innerText = "Ошибка!"
  140. console.error(response)
  141. }
  142. },
  143. onerror: (error) => {
  144. console.error(error);
  145. }
  146. });
  147. }
  148. const getSubmissions = () => {
  149. return JSON.parse(localStorage.getItem("c_status") ?? "[]");
  150. };
  151. const isSubAccepted = (subId) => {
  152. return getSubmissions().includes(subId)
  153. }
  154.  
  155. const acceptSubmission = (subId, button) => {
  156. const type = isSubAccepted(subId) ? "star" : "accept";
  157. acceptSheetSubmission(subId, button, type);
  158. }
  159.  
  160. const showed_codes = {};
  161.  
  162. function createSubShowButton(subId) {
  163. const button = document.createElement("button");
  164. button.className = "submission-show";
  165. button.style.marginTop = "10px";
  166. button.style.backgroundColor = "#176F95";
  167. button.style.border = "1px solid #176F95";
  168. button.style.borderRadius = "8px"
  169. button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  170. button.style.color = "#fff"
  171. button.style.cursor = "pointer"
  172. button.style.padding = "8px 0.5em"
  173. button.style.width = "100%";
  174. button.innerText = "Показать код";
  175. button.onclick = (_) => showButtonClick(subId, button);
  176. return button;
  177. }
  178.  
  179. function patchCodeSection(subId) {
  180. const patchLine = (i, line) => {
  181. line.addEventListener('click', () => {
  182. const text = $("[data-submission-id=" + subId + "] textarea")
  183. text.val((text.val().length === 0 ? "" : text.val() + "\n") + "Строка " + i + ": ")
  184. text.focus();
  185. });
  186. return line;
  187. };
  188.  
  189. let pretty_code = $('[data-submission-id=' + subId + '] .program-source li');
  190. const code_lines_count = pretty_code.length
  191. pretty_code.each((i, line) => patchLine(i, line))
  192. pretty_code = pretty_code.parent().parent();
  193. pretty_code.before((_) => {
  194. const lines = document.createElement("pre");
  195. lines.style.width = '4%';
  196. lines.style.padding = "0.5em";
  197. lines.style.display = 'inline-block';
  198. const lineNs = [...Array(code_lines_count).keys()].map((i) => {
  199. const line = document.createElement("span");
  200. line.style.color = 'rgb(153, 153, 153)';
  201. line.innerText = "[" + i + "]";
  202. line.style.display = "block";
  203. line.style.textAlign = "right";
  204. line.style.userSelect = "none";
  205. line.style.cursor = "pointer";
  206. return patchLine(i, line);
  207. })
  208. lines.append(...lineNs)
  209. return lines
  210. })
  211. pretty_code.css({'display': 'inline-block', 'width': '90%'})
  212. }
  213.  
  214. function showButtonClick(subId, button) {
  215. if (showed_codes[subId] != null) {
  216. if (showed_codes[subId].showed == true) {
  217. $(showed_codes[subId]["commentSection"]).hide();
  218. $(showed_codes[subId]["codeSection"]).hide();
  219. button.innerText = "Показать код";
  220. showed_codes[subId].showed = false;
  221. } else if (showed_codes[subId].showed == false) {
  222. $(showed_codes[subId]["commentSection"]).show();
  223. $(showed_codes[subId]["codeSection"]).show();
  224. button.innerText = "Скрыть код";
  225. showed_codes[subId].showed = true;
  226. }
  227. } else {
  228. button.innerText = showed_codes[subId] = "Загружаю код..."
  229. const requestUrl = getContestUrl() + 'submission/' + subId;
  230. console.log(requestUrl);
  231. $.get(requestUrl, function (html) {
  232. const codeHtml = $(html).find(".SubmissionDetailsFrameRoundBox-" + subId).html()
  233.  
  234. if (codeHtml == undefined) {
  235. button.innerText = "Ошибка!";
  236. //location.reload();
  237. return;
  238. }
  239.  
  240. const commentSection = createCommentSection(subId)
  241. const subCodeSection = createSubCodeSection(subId, codeHtml);
  242.  
  243. const subRow = getSubRow(subId);
  244. subRow.after(commentSection, subCodeSection)
  245.  
  246. prettyPrint(subId);
  247.  
  248. patchCodeSection(subId);
  249.  
  250. showed_codes[subId] = {
  251. "showed": true,
  252. "showButton": button,
  253. "commentSection": commentSection,
  254. "codeSection": subCodeSection
  255. }
  256. button.innerText = "Скрыть код"
  257. });
  258. }
  259. }
  260.  
  261. function createSubCodeSection(subId, codeHtml) {
  262. const trSubCode = document.createElement("tr");
  263. trSubCode.setAttribute('data-submission-id', subId);
  264. const tdSubCode = document.createElement("td");
  265. tdSubCode.setAttribute('colspan', '8');
  266. tdSubCode.innerHTML = codeHtml;
  267. tdSubCode.style.textAlign = "start"
  268. trSubCode.append(tdSubCode);
  269. return trSubCode;
  270. }
  271.  
  272. const createCommentSection = (subId) => {
  273. const subAcButton = getSubAcButton(subId)[0];
  274. const isAccepted = isSubAccepted(subId);
  275. const commentTextfield = createCommentTextfield()
  276. const commentAcButton = commentSendButtonTemplate(subId, (isAccepted ? "Похвалить" : "Принять") + " с комментарием", (isAccepted ? "#81D718" : "#13aa52"), (subId, button) => {
  277. const text = $(commentTextfield).val();
  278. if (text.length === 0) {
  279. button.innerText = "Принимаю...";
  280. acceptSubmission(subId, subAcButton);
  281. } else {
  282. button.innerText = "Отправляю...";
  283. $.post(getGroupUrl() + 'data/newAnnouncement', {
  284. contestId: getContestId(),
  285. englishText: "",
  286. russianText: text,
  287. submittedProblemIndex: getProblemIndex(subId),
  288. targetUserHandle: getHandle(subId),
  289. announceInPairContest: true,
  290. }, () => {
  291. acceptSubmission(subId, subAcButton);
  292. })
  293. }
  294. })
  295. const commentRjButton = commentSendButtonTemplate(subId, "Отклонить с комментарием", "#EC431A", (subId, button) => {
  296. const text = $(commentTextfield).val();
  297. if (text.length > 0) {
  298. button.innerText = "Отклоняю...";
  299. $.post(getGroupUrl() + 'data/newAnnouncement', {
  300. contestId: getContestId(),
  301. englishText: "",
  302. russianText: text,
  303. submittedProblemIndex: getProblemIndex(subId),
  304. targetUserHandle: getHandle(subId),
  305. announceInPairContest: true,
  306. }, () => {
  307. rejectSub(subId);
  308. if (showed_codes[subId] != null) {
  309. $(showed_codes[subId]["codeSection"]).hide();
  310. }
  311. button.innerText = "Отклонено";
  312. })
  313. }
  314. });
  315.  
  316. commentTextfield.addEventListener("keyup", (event) => {
  317. event.preventDefault();
  318. if (event.keyCode === 13) {
  319. commentRjButton.click();
  320. }
  321. });
  322.  
  323. const trSection = document.createElement("tr");
  324. trSection.setAttribute('data-submission-id', subId);
  325. const tdSection = document.createElement("td");
  326. tdSection.setAttribute('colspan', '8');
  327. const tdSectionTitle = document.createElement("div");
  328. tdSectionTitle.style.textAlign = "left";
  329. tdSectionTitle.className = "caption titled"
  330. tdSectionTitle.innerText = "→ Комментарий"
  331. tdSection.append(tdSectionTitle, commentTextfield, commentAcButton, commentRjButton);
  332. trSection.append(tdSection)
  333. return trSection;
  334. }
  335.  
  336. function createCommentTextfield() {
  337. const textField = document.createElement("textarea");
  338. textField.name = "russianText"
  339. textField.className = "bottom-space-small monospaced"
  340. textField.style.width = "80rem";
  341. textField.style.height = "5rem";
  342. textField.style.margin = "4px";
  343. return textField;
  344. }
  345.  
  346. const commentSendButtonTemplate = (subId, text, color, action) => {
  347. const button = document.createElement("button");
  348. button.className = SUB_COMMENT_SEND_BUTTON_CLASS;
  349. button.style.margin = "4px";
  350. button.style.width = "40%";
  351. button.style.backgroundColor = color;
  352. button.style.border = "1px solid " + color;
  353. button.style.borderRadius = "8px"
  354. button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  355. button.style.color = "#fff"
  356. button.style.cursor = "pointer"
  357. button.style.padding = "8px 0.5em"
  358. button.innerText = text;
  359. button.onclick = () => action(subId, button);
  360. return button
  361. }
  362.  
  363. const acButtonTemplate = (subId, action, text) => {
  364. const acButton = document.createElement("button");
  365. acButton.className = SUB_AC_BUTTON_CLASS;
  366. const color = (isSubAccepted(subId) ? "#81D718" : "#13aa52");
  367. acButton.style.backgroundColor = color;
  368. acButton.style.border = "1px solid " + color;
  369. acButton.style.borderRadius = "8px"
  370. acButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  371. acButton.style.color = "#fff"
  372. acButton.style.cursor = "pointer"
  373. acButton.style.padding = "8px 0.5em"
  374. acButton.style.margin = "5px 5px 0 0";
  375. acButton.style.width = "59%";
  376. acButton.innerText = text !== undefined ? text : (isSubAccepted(subId) ? "Похвалить" : "Принять");
  377. acButton.onclick = (_) => action(subId, acButton);
  378. return acButton;
  379. }
  380.  
  381. const createAcButton = (template, subId, ...args) => {
  382. return template(subId, (subId, button) => {
  383. button.innerText = "Подтверждаю...";
  384. button.style.borderColor = "gray";
  385. button.style.backgroundColor = "gray";
  386. acceptSubmission(subId, button);
  387. }, ...args);
  388. }
  389.  
  390. const createRjButton = (subId, text, action) => {
  391. const rjButton = document.createElement("button");
  392. rjButton.className = SUB_RJ_BUTTON_CLASS;
  393. rjButton.style.width = "38%";
  394. rjButton.style.backgroundColor = "#EC431A";
  395. rjButton.style.border = "1px solid #EC431A"
  396. rjButton.style.borderRadius = "8px"
  397. rjButton.style.padding = "8px 0.5em"
  398. rjButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  399. rjButton.style.color = "#fff"
  400. rjButton.style.cursor = "pointer"
  401. rjButton.innerText = text;
  402. rjButton.onclick = (_) => action(subId, rjButton);
  403. return rjButton
  404. }
  405.  
  406. function createRgButtonFromTemplate(subId, text, action) {
  407. const button = document.createElement("button");
  408. button.className = SUB_RG_BUTTON_CLASS;
  409. button.style.marginTop = "7px";
  410. button.style.width = "200px";
  411. button.innerText = text;
  412. button.onclick = (_) => action(subId, button);
  413. return button;
  414. }
  415.  
  416. const createRgButton = (subId) => {
  417. return createRgButtonFromTemplate(subId, "Перетестировать", (subId, button) => {
  418. const requestUrl = getContestUrl() + 'submission/' + subId
  419. const data = {action: "rejudge", submissionId: subId}
  420. $.post(requestUrl, data, (_) => location.reload());
  421. button.innerText = "Тестирую...";
  422. });
  423. }
  424.  
  425. const rejectSub = (subId) => {
  426. const subRjButton = getSubRjButton(subId);
  427. subRjButton.innerText = "Отклоняю...";
  428. const subAcButton = getSubAcButton(subId);
  429. const commentSubAcButton = getCommentSubAcButton(subId);
  430. const requestUrl = getContestUrl() + 'submission/' + subId
  431. const data = {action: "reject", submissionId: subId}
  432. $.post(requestUrl, data, function (_) {
  433. $("[submissionid=" + subId + "] .verdict-accepted").remove()
  434. subAcButton.remove()
  435. subRjButton.remove()
  436. commentSubAcButton.remove();
  437. })
  438. }
  439.  
  440. const patchSubmissions = () => {
  441. const subsId = getSubsId();
  442. subsId.after((i) => {
  443. const subId = Number($(subsId[i])[0].getAttribute('submissionid'))
  444. return createSubShowButton(subId);
  445. })
  446. }
  447.  
  448. const patchCorrectSubmissions = () => {
  449. const correctSubs = getCorrectSubs();
  450. correctSubs.after((i) => {
  451. const subId = Number($(correctSubs[i]).attr('submissionid'))
  452.  
  453. const acButton = createAcButton(acButtonTemplate, subId)
  454. const rgButton = createRgButton(subId);
  455. const rjButton = createRjButton(subId, "Отклонить", (subId, _) => {
  456. rejectSub(subId);
  457. });
  458.  
  459. return [acButton, rjButton, rgButton]
  460. })
  461. }
  462.  
  463. const patchContestSidebar = () => {
  464. const contestsSidebar = $(".GroupContestsSidebarFrame ul a")
  465. contestsSidebar.before((i) => {
  466. const contestHref = $(contestsSidebar[i]).attr('href');
  467. return document.createTextNode(wrap(getLast(contestHref)));
  468. });
  469. }
  470.  
  471. const patchSubmission = () => {
  472. const buttons = getSubButtons()
  473. if (buttons.length > 0) {
  474. const subId = Number(getLast(location.pathname));
  475. const acButton = createAcButton(acButtonTemplate, subId);
  476. buttons[0].before(acButton);
  477. }
  478. }
  479.  
  480. const patchFilterBox = () => {
  481. const filterBox = getFilterBox();
  482. const sidebar = getSideBar();
  483. filterBox.detach().prependTo(sidebar);
  484. const filterBoxPart = filterBox.find(".status-filter-form-part")[0];
  485.  
  486. const correctSubsId = getCorrectSubs().map((i, e) => Number($(e).attr("submissionid"))).toArray();
  487. const filter = (checkbox) => {
  488. localStorage.setItem("filterPendingSubs", checkbox.checked);
  489. const filtered = correctSubsId.filter(subId => {
  490. console.log(subId + " " + !isSubAccepted(subId))
  491. return !isSubAccepted(subId)
  492. });
  493. console.log(filtered)
  494. getAllSubmissionsRow().each((i, e) => {
  495. if (checkbox.checked) {
  496. if (!filtered.includes(Number($(e).attr('data-submission-id')))) {
  497. $(e).hide();
  498. }
  499. } else {
  500. $(e).show()
  501. }
  502. });
  503. };
  504.  
  505. const template = createFilterPendingCheckboxTemplate(filter);
  506. const label = template[0]
  507. const checkbox = template[1]
  508. checkbox.checked = ('true' === localStorage.getItem("filterPendingSubs") ?? false);
  509. filter(checkbox);
  510. filterBoxPart.before(label);
  511. }
  512.  
  513. function createFilterPendingCheckboxTemplate(action) {
  514. const label = document.createElement("label");
  515. const checkbox = document.createElement("input");
  516. checkbox.type = "checkbox";
  517. checkbox.onclick = (_) => action(checkbox);
  518. const title = document.createElement("span");
  519. title.style.padding = "5px";
  520. title.className = "smaller";
  521. title.innerText = "Только непроверенные посылки";
  522. label.append(checkbox, title);
  523. return [label, checkbox];
  524. }
  525.  
  526.  
  527. (function () {
  528. getSheetSubmissions();
  529.  
  530. try {
  531. patchContestSidebar();
  532. } catch (e) {
  533. console.error(e);
  534. }
  535.  
  536. try {
  537. patchFilterBox();
  538. } catch (e) {
  539. console.error(e);
  540. }
  541.  
  542. try {
  543. patchCorrectSubmissions();
  544. } catch (e) {
  545. console.error(e);
  546. }
  547.  
  548. try {
  549. patchSubmissions();
  550. } catch (e) {
  551. console.error(e);
  552. }
  553.  
  554. try {
  555. patchSubmission();
  556. } catch (e) {
  557. console.error(e);
  558. }
  559. })();

QingJ © 2025

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