Greasy Fork 还支持 简体中文。

Codeforces Gym Statistic

gym做题信息统计

  1. // ==UserScript==
  2. // @name Codeforces Gym Statistic
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description gym做题信息统计
  6. // @author Tanphoon
  7. // @match https://codeforces.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=codeforces.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. (function () {
  13. 'use strict';
  14. const pathname = window.location.pathname;
  15. console.log(pathname);
  16. if (pathname.startsWith("/contests/with/")) {
  17. gymAnalyze();
  18. }
  19. function gymAnalyze() {
  20. const handle = pathname.substring(pathname.lastIndexOf('/') + 1, pathname.length);
  21. // 定义请求的URL
  22. const userDataUrl = `https://codeforces.com/api/user.status?handle=${handle}`;
  23. const contestListUrl = 'https://codeforces.com/api/contest.list?gym=true';
  24. // 使用fetch函数获取JSON数据,并将其转换为JSON对象
  25. async function fetchData(url) {
  26. try {
  27. const response = await fetch(url);
  28. if (!response.ok) {
  29. throw new Error(`HTTP error! status: ${response.status}`);
  30. }
  31. return response.json();
  32. } catch (error) {
  33. console.error(`Fetch error for ${url}: `, error);
  34. throw error;
  35. }
  36. };
  37. async function getContestData() {
  38. const today = new Date().toLocaleDateString();
  39. // 比赛信息
  40. let contestData = {};
  41. if (!localStorage.getItem("ContestDataVersion") || localStorage.getItem("ContestDataVersion") != today) {
  42. try {
  43. const contestList = await fetchData(contestListUrl);
  44. contestList.result.forEach(item => {
  45. contestData[item.id] = item.name;
  46. });
  47. localStorage.setItem("ContestDataVersion", today);
  48. localStorage.setItem("ContestData", JSON.stringify(contestData));
  49. } catch (error) {
  50. console.error("An error occurred while fetching data:", error);
  51. }
  52. } else {
  53. contestData = JSON.parse(localStorage.getItem("ContestData"));
  54. }
  55. return contestData;
  56. }
  57. async function main() {
  58. const contestData = await getContestData();
  59. const userData = await fetchData(userDataUrl);
  60. let data = {};
  61. userData.result.forEach(item => {
  62. // 提取所需字段
  63. const commitId = item.id;
  64. const contestId = item.contestId;
  65. const time = new Date(item.creationTimeSeconds * 1000).toLocaleString();
  66. const verdict = item.verdict;
  67. const problemId = item.problem.index;
  68. if (contestId > 100000) {
  69. if (!data[contestId]) {
  70. data[contestId] = { 'name': contestData[contestId], 'correct': [], 'problem': {}, };
  71. }
  72. // 通过的题目
  73. if (verdict == "OK" && !data[contestId]['correct'].includes(problemId))
  74. data[contestId]['correct'].push(problemId);
  75. // 题目提交信息
  76. if (!data[contestId]['problem'][problemId]) {
  77. data[contestId]['problem'][problemId] = [];
  78. }
  79. data[contestId]['problem'][problemId].push({ commitId, time, verdict });
  80. }
  81. });
  82. // 用户信息
  83. drawTable(data);
  84. console.log(data);
  85. }
  86. function drawTable(data) {
  87. const facebox = document.createElement('style');
  88. facebox.textContent = `
  89. .popup {
  90. display: none;
  91. position: fixed;
  92. z-index: 99;
  93. left: 0;
  94. top: 0;
  95. width: 100%;
  96. height: 100%;
  97. overflow: auto;
  98. background-color: rgba(0, 0, 0, 0.4);
  99. }
  100.  
  101. .popup-content {
  102. background-color: #fefefe;
  103. position: fixed;
  104. top: 50%;
  105. left: 50%;
  106. transform: translate(-50%, -50%);
  107. padding: 10px;
  108. width: 50%;
  109. border-radius: 5px;
  110. }
  111.  
  112. .close {
  113. color: #aaa;
  114. float: right;
  115. font-size: 28px;
  116. font-weight: bold;
  117. }
  118.  
  119.  
  120. .close:hover,
  121. .close:focus {
  122. color: black;
  123. text-decoration: none;
  124. cursor: pointer;
  125. }
  126. .content {
  127. border: 1px solid #888;
  128. padding: 20px;
  129. border-radius: 5px;
  130. }
  131.  
  132. #gym th, td {
  133. cursor: pointer;
  134. }
  135. `;
  136. document.head.appendChild(facebox);
  137. let datatable = `
  138. <div id="gyms" class="datatable" style="background-color: #E1E1E1; padding-bottom: 3px;">
  139. <div class="lt">&nbsp;</div>
  140. <div class="rt">&nbsp;</div>
  141. <div class="lb">&nbsp;</div>
  142. <div class="lb">&nbsp;</div>
  143. <div style="padding: 4px 0 0 6px;font-size:1.4rem;position:relative;">Gyms</div>
  144. <div style="background-color: white;margin:0.3em 3px 0 3px;position:relative;">
  145. <div class="ilt">&nbsp;</div>
  146. <div class="irt">&nbsp;</div>
  147. <table class="tablesorter user-contests-table">
  148. <thead>
  149. <tr>
  150. <th class="top left">#</th>
  151. <th class="top">Contest</th>
  152. ${Array.from("ABCDEFGHIJKLM").map(problemId => `<th class="top">${problemId}</th>`).join('')}
  153. <th class="top right">Solved</th>
  154. </tr>
  155. </thead>
  156. <tbody>
  157. ${Object.keys(data).map((contestId, index) => `
  158. <tr>
  159. <td>${index + 1}</td>
  160. <td><a href="/gym/${contestId}">${data[contestId]['name']}</a></td>
  161. ${Array.from("ABCDEFGHIJKLM").map(problemId => {
  162. const correct = data[contestId]['correct'].includes(problemId);
  163. const style = correct ? "font-weight: bold;color:#0a0" : "color:#00a";
  164. if (data[contestId]['problem'][problemId]) {
  165. return `
  166. <td style="${style}" problemid=${problemId}>
  167. ${correct ? '+' : '-'}${data[contestId]['problem'][problemId].length}
  168. </td>`;
  169. }
  170. else {
  171. return `<td problemid=${problemId}></td>`;
  172. }
  173. }).join('')}
  174. <td>${Object.keys(data[contestId]['problem']).length}</td>
  175. </tr>
  176. `).join('')}
  177. </tbody>
  178. </table>
  179. </div>
  180. </div>
  181. `;
  182. let contests = document.querySelector("#pageContent > .datatable");
  183. contests.insertAdjacentHTML("afterend", datatable);
  184. let gyms = document.querySelector("#pageContent > #gyms");
  185. contests.style.display = 'block';
  186. gyms.style.display = 'none';
  187. document.querySelector("#pageContent > div.second-level-menu > ul > li.current.selectedLava").insertAdjacentHTML("afterend", "<li><a id='toggleButton' style='cursor: pointer;'>SWITCH TO GYMS</a></li>")
  188.  
  189. const toggleButton = document.getElementById('toggleButton');
  190. toggleButton.addEventListener('click', () => {
  191. if (contests.style.display === 'none') {
  192. contests.style.display = 'block';
  193. gyms.style.display = 'none';
  194. toggleButton.textContent = 'SWITCH TO GYMS';
  195. } else {
  196. contests.style.display = 'none';
  197. gyms.style.display = 'block';
  198. toggleButton.textContent = 'SWITCH TO COMTESTS';
  199. }
  200. });
  201.  
  202. let popdiv = `
  203. <div id="mypopup" class="popup">
  204. <div class="popup-content">
  205. <span class="close">&times;</span>
  206. <div class="content" id="popupMessage">
  207. <h2>This is the popup content</h2>
  208. <p>You can add any content inside the popup.</p>
  209. </div>
  210. </div>
  211. </div>
  212. `;
  213. gyms.insertAdjacentHTML('afterend', popdiv);
  214.  
  215. const table = document.getElementById("gyms");
  216. const popup = document.getElementById("mypopup");
  217. const closeBtn = document.querySelector(".close");
  218. const popupMessage = document.getElementById("popupMessage");
  219. const columnNames = Array.from(table.getElementsByTagName("th")).map(th => th.textContent);
  220. table.addEventListener("click", (event) => {
  221. const cell = event.target.closest("td");
  222. if (cell) {
  223. const contestId = Object.keys(data)[cell.parentNode.rowIndex - 1];
  224. const problemId = cell.getAttribute('problemid');
  225. const commitInfos = data[contestId]['problem'][problemId];
  226. if (commitInfos) {
  227. popupMessage.innerHTML = commitInfos.map(item => `
  228. ${item.time}
  229. &nbsp;
  230. <span style="${item.verdict == "OK" ? "font-weight: bold;color:#0a0" : "color:#00a"}">${item.verdict == "OK" ? "Accepted" : item.verdict.replace("/^\w/", (match) => match.toUpperCase())}</span>
  231. <a href=https://cf.dianhsu.com/gym/${contestId}/submission/${item.commitId} target="_blank">${item.commitId}</a><br>
  232. `).join('');
  233. popup.style.display = "block";
  234. }
  235. }
  236. });
  237. closeBtn.addEventListener("click", () => {
  238. popup.style.display = "none";
  239. });
  240. popup.addEventListener("click", (event) => {
  241. if (event.target === popup) {
  242. popup.style.display = "none";
  243. }
  244. });
  245. }
  246. main();
  247. }
  248. })();