Greasy Fork is available in English.

AtCoderDifficultyDisplay

display a difficulty of AtCoder Problems.

2020-06-07 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name AtCoderDifficultyDisplay
  3. // @namespace https://github.com/hotarunx
  4. // @version 0.6
  5. // @description display a difficulty of AtCoder Problems.
  6. // @description:ja AtCoder Problemsの難易度を表示します。
  7. // @author hotarunx
  8. // @match https://atcoder.jp/contests/*/tasks/*
  9. // @grant none
  10. // @connect https://kenkoooo.com/atcoder/resources/*
  11. // @connect https://kenkoooo.com/atcoder/atcoder-api/*
  12. // @license MIT
  13. //
  14. // Copyright(c) 2020 hotarunx
  15. // This software is released under the MIT License, see LICENSE or https://github.com/hotarunx/AtCoderMyExtensions/blob/master/LICENSE.
  16. //
  17. // ==/UserScript==
  18.  
  19. (function () {
  20.  
  21. // -------------------------------------------------------------------------
  22. // 設定
  23. // 次の変数の値を書き換えることで各数値を表示するかどうかを変更できます
  24.  
  25. // 難易度を表示するかどうか
  26. const displayDifficulty = true;
  27.  
  28. // 提出状況を表示するかどうか
  29. const displaySubmissionStatus = true;
  30.  
  31. // true: 表示する
  32. // false: 表示しない
  33. // -------------------------------------------------------------------------
  34.  
  35. // URL of Estimated difficulties of the problems
  36. const SUBMISSION_API = "https://kenkoooo.com/atcoder/atcoder-api/results?user=" + userScreenName;
  37. const SUBMISSIONS_DATASET = "https://kenkoooo.com/atcoder/resources/problem-models.json";
  38.  
  39. if (displayDifficulty)
  40. fetch(SUBMISSIONS_DATASET)
  41. .then((response) => response.json())
  42. .then((jsonData) => {
  43. addDifficultyText(jsonData);
  44. });
  45.  
  46. if (displaySubmissionStatus && userScreenName != "")
  47. fetch(SUBMISSION_API)
  48. .then((response) => response.json())
  49. .then((submissionData) => {
  50. addSubmissionStatusText(submissionData);
  51. });
  52.  
  53. })();
  54.  
  55. // Webページの問題ステータス(実行時間制限とメモリ制限が書かれた部分)のHTMLオブジェクトを取得
  56. function getElementOfProblemStatus() {
  57. let element_status;
  58.  
  59. const main_container = document.getElementById('main-container');
  60. const elements_p = main_container.getElementsByTagName("p");
  61.  
  62. for (let i = 0; i < elements_p.length; i++) {
  63. const element = elements_p[i];
  64. if (element.textContent.match("メモリ制限:") || element.textContent.match("Memory Limit:")) {
  65. element_status = element;
  66. break
  67. }
  68. }
  69.  
  70. return element_status;
  71. }
  72.  
  73. // レーティングに対応する色のカラーコードを返す
  74. function colorRating(rating) {
  75. let color;
  76. if (rating < 400) color = '#808080'; // gray
  77. else if (rating < 800) color = '#804000'; // brown
  78. else if (rating < 1200) color = '#008000'; // green
  79. else if (rating < 1600) color = '#00C0C0'; // cyan
  80. else if (rating < 2000) color = '#0000FF'; // blue
  81. else if (rating < 2400) color = '#C0C000'; // yellow
  82. else if (rating < 2800) color = '#FF8000'; // orange
  83. else color = '#FF0000'; // red
  84.  
  85. return color;
  86. }
  87.  
  88. // 難易度円→◒の文章を生成する
  89. function generateDifficultyCircle(rating) {
  90.  
  91. if (rating < 3200) {
  92. // 色と円がどのぐらい満ちているかを計算
  93. const color = colorRating(rating);
  94. const percentFull = (rating % 400) / 400 * 100;
  95.  
  96. // ◒を生成
  97. return "<span style = 'display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px;border-color: " + color + "; background: linear-gradient(to top, " + color + " 0%, " + color + " " + percentFull + "%, rgba(0, 0, 0, 0) " + percentFull + "%, rgba(0, 0, 0, 0) 100%); '></span>"
  98.  
  99. }
  100. // 金銀銅は例外処理
  101. else if (rating < 3600) {
  102. return '<span style="display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px; border-color: rgb(150, 92, 44); background: linear-gradient(to right, rgb(150, 92, 44), rgb(255, 218, 189), rgb(150, 92, 44));"></span>';
  103.  
  104. } else if (rating < 4000) {
  105. return '<span style="display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px; border-color: rgb(128, 128, 128); background: linear-gradient(to right, rgb(128, 128, 128), white, rgb(128, 128, 128));"></span>';
  106.  
  107. } else {
  108. return '<span style="display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px; border-color: rgb(255, 215, 0); background: linear-gradient(to right, rgb(255, 215, 0), white, rgb(255, 215, 0));"></span>';
  109.  
  110. }
  111. }
  112.  
  113. // レーティングを0以上に補正
  114. // 参考 https://qiita.com/anqooqie/items/92005e337a0d2569bdbd#%E6%80%A7%E8%B3%AA4-%E5%88%9D%E5%BF%83%E8%80%85%E3%81%B8%E3%81%AE%E6%85%88%E6%82%B2
  115. function correctLowerRating(rating) {
  116. if (rating >= 400) return rating;
  117. do {
  118. rating = 400 / Math.exp((400 - rating) / 400);
  119. } while (rating < 0);
  120. return rating;
  121. }
  122.  
  123. // 難易度を表示する文字列を生成
  124. function generateDifficultyText(difficulty, is_experimental) {
  125. // 難易度を0にして四捨五入
  126. difficulty = correctLowerRating(difficulty);
  127. difficulty = difficulty.toFixed();
  128.  
  129. // テキストを生成
  130. let difficultyText = "Difficulty: ";
  131. if (is_experimental) difficultyText = "🧪" + difficultyText;
  132. difficultyText += difficulty;
  133. difficultyText += generateDifficultyCircle(difficulty);
  134.  
  135. // 色つけ
  136. const color = colorRating(difficulty);
  137. difficultyText = "<span style='color: " + color + ";'>" + difficultyText + "</span>";
  138.  
  139. // Problemsへのリンクを追加
  140. const atcoderProblemsUrl = "https://kenkoooo.com/atcoder/#/table/" + userScreenName;
  141. difficultyText = "<a href='" + atcoderProblemsUrl + "'>" + difficultyText + "</a>";
  142.  
  143. return " / " + difficultyText;
  144. }
  145.  
  146. function addDifficultyText(jsonData) {
  147. // URLから問題IDを取得
  148. const path = location.pathname.split("/");
  149. const id = path[path.length - 1];
  150. // 問題データを取得
  151. const problem = jsonData[id];
  152.  
  153. // 問題が存在しなければ終了
  154. if (problem == null || problem.difficulty == null) { return; }
  155.  
  156. // 難易度を表示する文字列を生成
  157. const text = generateDifficultyText(problem.difficulty, problem.is_experimental);
  158.  
  159. // 問題ステータスのHTMLオブジェクトを探してtextを追加
  160. let status = getElementOfProblemStatus();
  161. status.insertAdjacentHTML('beforeend', text);
  162. }
  163.  
  164. function addSubmissionStatusText(submissionData) {
  165. // URLから問題IDを取得
  166. const path = location.pathname.split("/");
  167. const id = path[path.length - 1];
  168.  
  169. // コンテスト時間を取得
  170. const start = Math.floor(Date.parse(startTime._i) / 1000);
  171. const end = Math.floor(Date.parse(endTime._i) / 1000);
  172.  
  173. // 4つの提出状況記録変数
  174. // コンテスト中にACした、コンテスト外にACした、コンテスト中に提出した、コンテスト外に提出した
  175. let contestAccepted = false, accepted = false, contestSubmitted = false, submitted = false;
  176.  
  177. let latestAcceptedSubmission, latestSubmission;
  178.  
  179. // この問題への提出をすべて探索して提出状況を更新する
  180. const submissions = submissionData.filter(function (item, index) { if (item.problem_id == id) return true; });
  181. submissions.sort((a, b) => a.epoch_second - b.epoch_second);
  182.  
  183. for (const item of submissions) {
  184. const time = item["epoch_second"];
  185. const isDuringContest = start <= time && time <= end;
  186. const isAccepted = item["result"] == "AC";
  187.  
  188. if (isDuringContest) {
  189. contestSubmitted = true;
  190. if (isAccepted) contestAccepted = true;
  191. } else {
  192. submitted = true;
  193. if (isAccepted) accepted = true;
  194. }
  195.  
  196. if (isAccepted) latestAcceptedSubmission = item;
  197. else latestSubmission = item;
  198. }
  199.  
  200. // 提出状況を表す文字列を生成
  201. let text;
  202. if (contestAccepted) text = "<span style='color: #5CB85C;'>★Accepted</span>";
  203. else if (accepted) text = "<span style='color: #5CB85C;'>Accepted</span>";
  204. else if (submitted) text = "<span style='color: #F0AD4E;'>Trying</span>";
  205. else if (contestSubmitted) text = "<span style='color: #F0AD4E;'>★Trying</span>";
  206. else text = "Trying";
  207.  
  208. // 最新のAC提出または提出へのリンクを追加
  209. if (submitted || contestSubmitted) {
  210. const submission = (latestAcceptedSubmission != null ? latestAcceptedSubmission : latestSubmission);
  211.  
  212. const url = "https://atcoder.jp/contests/" + submission.contest_id + "/submissions/" + submission.id;
  213.  
  214. text = "<a href='" + url + "'>" + text + "</a>";
  215. }
  216.  
  217. // 問題ステータスのHTMLオブジェクトを探してtextを追加
  218. let status = getElementOfProblemStatus();
  219. status.insertAdjacentHTML('beforeend', " / " + text);
  220. }