atcoder-standings-lang

AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです.

当前为 2020-11-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name atcoder-standings-lang
  3. // @namespace iilj
  4. // @version 2020.11.10.0
  5. // @description AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです.
  6. // @author iilj
  7. // @supportURL https://github.com/iilj/atcoder-standings-lang/issues
  8. // @match https://atcoder.jp/contests/*/standings*
  9. // ==/UserScript==
  10.  
  11. /* globals $ */
  12.  
  13. /**
  14. * ユーザID/言語ごとの提出数
  15. * @typedef {Object} UserLangEntry
  16. * @property {string} user_id ユーザ ID
  17. * @property {string} language 言語名
  18. * @property {number} count 提出数
  19. */
  20.  
  21. (() => {
  22. 'use strict';
  23.  
  24. /** @type {Map<string, UserLangEntry[]>} */
  25. const userLangEntryMap = new Map();
  26.  
  27. const fetchLangJson = async () => {
  28. console.log('@kenkooooさんありがとう');
  29. console.log('うえすぎさんありがとう');
  30. // 2e5 個くらい要素があるのでキャッシュする
  31. const res = await fetch("https://kenkoooo.com/atcoder/resources/lang.json", { cache: 'force-cache' });
  32. /** @type {UserLangEntry[]} */
  33. const userLangEntries = await res.json();
  34.  
  35. // prepare map
  36. userLangEntries.forEach(userLangEntry => {
  37. if (userLangEntryMap.has(userLangEntry.user_id)) {
  38. userLangEntryMap.get(userLangEntry.user_id).push(userLangEntry);
  39. } else {
  40. userLangEntryMap.set(userLangEntry.user_id, [userLangEntry]);
  41. }
  42. });
  43.  
  44. // sort arrays
  45. userLangEntryMap.forEach(userLangArray => {
  46. userLangArray.sort((a, b) => b.count - a.count); // in place
  47. });
  48. };
  49.  
  50. /** @type {(anchor: HTMLAnchorElement) => void} */
  51. const updateAnchor = (anchor) => {
  52. if (!anchor.href.includes('/users/')) return;
  53.  
  54. const user_id = anchor.text.trim();
  55. if (!userLangEntryMap.has(user_id)) return;
  56.  
  57. const userLangArray = userLangEntryMap.get(user_id);
  58.  
  59. const tooltipHtml = userLangArray.map((userLangEntry) =>
  60. `${userLangEntry.language} : ${userLangEntry.count}`
  61. ).join('<br>');
  62.  
  63. let langHtml = userLangArray[0].language;
  64. if (userLangArray.length >= 2) {
  65. langHtml += '<div style="font-size:10px;display:inline;">'
  66. + `/${userLangArray[1].language}`
  67. + (userLangArray.length >= 3 ? `/${userLangArray[2].language}` : '')
  68. + ' </div>';
  69. }
  70.  
  71. anchor.insertAdjacentHTML('beforeend',
  72. '/'
  73. + '<div data-toggle="tooltip" data-html="true" data-placement="right" style="font-size:12px;display:inline;" title="' + tooltipHtml + '">'
  74. + langHtml
  75. + '</div>');
  76. $('[data-toggle="tooltip"]').tooltip();
  77. };
  78.  
  79. /** @type {(tbody: HTMLTableSectionElement) => void} */
  80. const updateTable = (tbody) => {
  81. tbody.querySelectorAll('.username').forEach(anchor => {
  82. updateAnchor(anchor);
  83. });
  84. };
  85.  
  86. /** @type {HTMLTableSectionElement} */
  87. let tbody = null;
  88.  
  89. const tableObserver = new MutationObserver(() => {
  90. updateTable(tbody);
  91. });
  92. const parentObserver = new MutationObserver(async () => {
  93. tbody = document.getElementById('standings-tbody');
  94. if (tbody) {
  95. parentObserver.disconnect();
  96. await fetchLangJson();
  97. updateTable(tbody);
  98. tableObserver.observe(
  99. tbody,
  100. { childList: true }
  101. )
  102. }
  103. });
  104. parentObserver.observe(
  105. document.getElementById('vue-standings'),
  106. { childList: true, subtree: true }
  107. );
  108. })();