Atcoder Duplicate Checker

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

As of 2022-11-12. See the latest version.

  1. // ==UserScript==
  2. // @name Atcoder Duplicate Checker
  3. // @namespace https://github.com/Raclamusi
  4. // @version 1.0.0
  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 getSubmissions = async contest => {
  41. const response = await fetch(`https://atcoder.jp/contests/${contest}/submissions/me`);
  42. const htmlText = await response.text();
  43. const iter = htmlText.matchAll(/<tr(?:.|\s)*?<time[^>]*>(.+?)<\/time>(?:.|\s)*?submissions\/(\d+)(?:.|\s)*?<\/tr>/g);
  44. return [...iter].map(([_, time, id]) => ({ time, id }));
  45. };
  46.  
  47. const getSubmittedCode = async (contest, id) => {
  48. const response = await fetch(`https://atcoder.jp/contests/${contest}/submissions/${id}`);
  49. const htmlText = await response.text();
  50. const escapedCode = htmlText.match(/<pre id="submission-code"[^>]*>([^<]*)<\/pre>/)[1];
  51. const preElement = document.createElement("pre");
  52. preElement.innerHTML = escapedCode;
  53. return preElement.textContent;
  54. };
  55.  
  56. const getDuplicateSubmisionTime = async code => {
  57. for (const { time, id } of await getSubmissions(contestScreenName)) {
  58. if (code === await getSubmittedCode(contestScreenName, id)) {
  59. return time;
  60. }
  61. }
  62. return null;
  63. };
  64.  
  65. const submitButton = document.getElementById("submit");
  66. const submitButtonClickListener = e => {
  67. // チェックを待つため、一旦提出をキャンセル
  68. e.preventDefault();
  69.  
  70. // チェック中であれば即終了
  71. if (submitButton.disabled) return;
  72.  
  73. // 提出ボタンの表示を変更
  74. submitButton.disabled = true;
  75. const buttonText = submitButton.textContent;
  76. submitButton.textContent = getButtonText(LANG);
  77.  
  78. (async () => {
  79. // 重複チェック
  80. const time = await getDuplicateSubmisionTime(getSourceCode());
  81. if (time === null || confirm(getMessage(LANG, time))) {
  82. // チェックが通ったら、このイベントリスナを外して本来の提出の処理をする
  83. submitButton.removeEventListener("click", submitButtonClickListener);
  84. setTimeout(() => submitButton.click());
  85. }
  86.  
  87. // 提出ボタンの表示を戻す
  88. submitButton.disabled = false;
  89. submitButton.textContent = buttonText;
  90. })();
  91. };
  92. submitButton.addEventListener("click", submitButtonClickListener, { passive: false });
  93. })();