Greasy Fork is available in English.

AtCoder Duplicate Checker

重複提出をチェックします。 Check for duplicate submissions.

  1. // ==UserScript==
  2. // @name AtCoder Duplicate Checker
  3. // @namespace https://github.com/Raclamusi
  4. // @version 1.0.2
  5. // @description 重複提出をチェックします。 Check for duplicate submissions.
  6. // @author Raclamusi
  7. // @supportURL https://github.com/Raclamusi/atcoder-duplicate-checker
  8. // @match https://atcoder.jp/contests/*/tasks/*
  9. // @match https://atcoder.jp/contests/*/submit*
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. // AtCoder Duplicate Checker
  15. //
  16. // Copyright (c) 2022 Raclamusi
  17. //
  18. // This software is released under the MIT License, see https://github.com/Raclamusi/atcoder-duplicate-checker/blob/main/LICENSE .
  19.  
  20. (function () {
  21. "use strict";
  22.  
  23. const getButtonText = lang => {
  24. const texts = {
  25. ja: "重複チェック中...",
  26. en: "Duplicate Checking...",
  27. };
  28. return texts[lang in texts ? lang : "ja"];
  29. };
  30.  
  31. const getMessage = (lang, time) => {
  32. const messages = {
  33. ja: ["過去に同じコードを提出しています。", "本当に提出しますか?"],
  34. en: ["You have submitted the same code before.", "Are you sure you want to submit it?"],
  35. };
  36. const msg = messages[lang in messages ? lang : "ja"];
  37. return `${msg[0]} (${time})\n${msg[1]}`;
  38. };
  39.  
  40. const aceEditor = ace.edit("editor");
  41. const plainEditor = document.getElementById("plain-textarea");
  42. const getSourceCode = () => {
  43. if (plainEditor.style.display === "none") {
  44. return aceEditor.getValue();
  45. }
  46. return plainEditor.value;
  47. };
  48.  
  49. const getSubmissions = async contest => {
  50. const response = await fetch(`https://atcoder.jp/contests/${contest}/submissions/me`);
  51. const htmlText = await response.text();
  52. const iter = htmlText.matchAll(/<tr(?:.|\s)*?<time[^>]*>(.+?)<\/time>(?:.|\s)*?submissions\/(\d+)(?:.|\s)*?<\/tr>/g);
  53. return [...iter].map(([_, time, id]) => ({ time, id }));
  54. };
  55.  
  56. const getSubmittedCode = async (contest, id) => {
  57. const response = await fetch(`https://atcoder.jp/contests/${contest}/submissions/${id}`);
  58. const htmlText = await response.text();
  59. const escapedCode = htmlText.match(/<pre id="submission-code"[^>]*>([^<]*)<\/pre>/)[1];
  60. const preElement = document.createElement("pre");
  61. preElement.innerHTML = escapedCode;
  62. return preElement.textContent;
  63. };
  64.  
  65. const getDuplicateSubmisionTime = async code => {
  66. for (const { time, id } of await getSubmissions(contestScreenName)) {
  67. if (code === await getSubmittedCode(contestScreenName, id)) {
  68. return time;
  69. }
  70. }
  71. return null;
  72. };
  73.  
  74. const submitButton = document.getElementById("submit");
  75. const submitButtonClickListener = e => {
  76. // チェックを待つため、一旦提出をキャンセル
  77. e.preventDefault();
  78.  
  79. // チェック中であれば即終了
  80. if (submitButton.disabled) return;
  81.  
  82. // 提出ボタンの表示を変更
  83. submitButton.disabled = true;
  84. const buttonText = submitButton.textContent;
  85. submitButton.textContent = getButtonText(LANG);
  86.  
  87. (async () => {
  88. // 重複チェック
  89. const time = await getDuplicateSubmisionTime(getSourceCode());
  90. if (time === null || confirm(getMessage(LANG, time))) {
  91. // チェックが通ったら、このイベントリスナを外して本来の提出の処理をする
  92. submitButton.removeEventListener("click", submitButtonClickListener);
  93. setTimeout(() => submitButton.click());
  94. }
  95.  
  96. // 提出ボタンの表示を戻す
  97. submitButton.disabled = false;
  98. submitButton.textContent = buttonText;
  99. })();
  100. };
  101. submitButton.addEventListener("click", submitButtonClickListener, { passive: false });
  102. })();