AtCoder Standings AC Time Formatter

AtCoderのコンテスト順位表に表示されるAC時間を "Dd H:MM:SS" にフォーマットする

  1. // ==UserScript==
  2. // @name AtCoder Standings AC Time Formatter
  3. // @namespace bo9chan
  4. // @version 1.0.1
  5. // @description AtCoderのコンテスト順位表に表示されるAC時間を "Dd H:MM:SS" にフォーマットする
  6. // @author bo9chan
  7. // @supportURL https://github.com/bo9chan/atcoder-standings-ac-time-formatter
  8. // @match https://atcoder.jp/contests/*/standings*
  9. // @exclude https://atcoder.jp/contests/*/standings/json
  10. // @license MIT
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. /**
  18. * @param {string} timeStr 時間を"MM:SS"の形式で指定した文字列
  19. * @returns {string} 時間を "Dd H:MM:SS", "H:MM:SS" または "M:SS" の形式で指定した文字列
  20. */
  21. function convertToDHMS(timeStr) {
  22. const [totalMinutes, seconds] = timeStr.split(':').map(Number);
  23. const secondsStr = String(seconds).padStart(2, '0');
  24. const minutes = totalMinutes % 60;
  25. const minutesStr = String(minutes).padStart(2, '0');
  26. const hours = Math.floor(totalMinutes / 60) % 24;
  27. const days = Math.floor(totalMinutes / (24 * 60));
  28. if (days > 0) {
  29. return `${days}d ${hours}:${minutesStr}:${secondsStr}`;
  30. } else if (hours > 0) {
  31. return `${hours}:${minutesStr}:${secondsStr}`;
  32. } else {
  33. return `${minutes}:${secondsStr}`;
  34. }
  35. }
  36.  
  37. let targetNode = null;
  38.  
  39. const handleMutation = () => {
  40. const selector1 = 'tr > td.standings-result > p:last-child';
  41. const selector2 = 'tr.standings-fa > td > p:last-child';
  42. // AC時間の要素を取得
  43. for (const selector of [selector1, selector2]) {
  44. const timeElements = targetNode.querySelectorAll(selector);
  45. if (timeElements.length > 0) {
  46. // AC時間のフォーマットを変換
  47. timeElements.forEach(el => {
  48. const originalTime = el.textContent.trim();
  49. if (originalTime.split(':').length == 2) {
  50. const convertedTime = convertToDHMS(originalTime);
  51. el.textContent = convertedTime;
  52. }
  53. });
  54. }
  55. }
  56. };
  57.  
  58. const parentHandleMutation = () => {
  59. targetNode = document.getElementById('standings-tbody');
  60. if (targetNode) {
  61. parentObserver.disconnect();
  62. const config = { childList: true, subtree: true };
  63. const observer = new MutationObserver((mutations, obs) => {
  64. // 再帰トリガーを止める
  65. obs.disconnect();
  66. // フォーマット処理を実行
  67. handleMutation();
  68. // 監視を再開
  69. obs.observe(targetNode, config);
  70. });
  71. handleMutation();
  72. observer.observe(targetNode, config);
  73. }
  74. }
  75.  
  76. const parentTargetNode = document.getElementById('vue-standings');
  77. const parentConfig = { childList: true, subtree: true };
  78. const parentObserver = new MutationObserver(parentHandleMutation);
  79. parentObserver.observe(parentTargetNode, parentConfig);
  80. })();