AtCoder Submission User Colorizer

提出一覧のユーザ名を色付けします

  1. // ==UserScript==
  2. // @name AtCoder Submission User Colorizer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description 提出一覧のユーザ名を色付けします
  6. // @author morio_prog
  7. // @match https://atcoder.jp/contests/*/submissions*
  8. // @grant none
  9. // @license CC0
  10. // @require https://unpkg.com/lscache/lscache.min.js
  11. // ==/UserScript==
  12.  
  13. $(function() {
  14. 'use strict';
  15.  
  16. const lastUpdateKey = 'user-colorizer-ranking-last-update';
  17. const rankingKey = 'user-colorizer-ranking';
  18. const OUT_OF_RANK = Number.MAX_VALUE; // > 100
  19.  
  20. function getColor(rating) {
  21. if (rating >= 2800) return '#FF0000';
  22. if (rating >= 2400) return '#FF8000';
  23. if (rating >= 2000) return '#C0C000';
  24. if (rating >= 1600) return '#0000FF';
  25. if (rating >= 1200) return '#00C0C0';
  26. if (rating >= 800) return '#008000';
  27. if (rating >= 400) return '#804000';
  28. if (rating > 0) return '#808080';
  29. return '#000000';
  30. }
  31.  
  32. function getColorClass(rating) {
  33. if (rating >= 2800) return 'user-red';
  34. if (rating >= 2400) return 'user-orange';
  35. if (rating >= 2000) return 'user-yellow';
  36. if (rating >= 1600) return 'user-blue';
  37. if (rating >= 1200) return 'user-cyan';
  38. if (rating >= 800) return 'user-green';
  39. if (rating >= 400) return 'user-brown';
  40. if (rating > 0) return 'user-gray';
  41. return 'user-unrated';
  42. }
  43.  
  44. function getAchRate(rating) {
  45. const base = Math.floor(rating / 400) * 400;
  46. return ((rating - base) / 400) * 100;
  47. }
  48.  
  49. function colorize(u, ranking, rating) {
  50. /* */if (ranking <= 1) $(u).before('<img style="vertical-align: middle;" src="//img.atcoder.jp/assets/icon/crown_champion.png">&nbsp;');
  51. else if (ranking <= 10) $(u).before('<img style="vertical-align: middle;" src="//img.atcoder.jp/assets/icon/crown_gold.png">&nbsp;');
  52. else if (ranking <= 30) $(u).before('<img style="vertical-align: middle;" src="//img.atcoder.jp/assets/icon/crown_silver.png">&nbsp;');
  53. else if (ranking <= 100) $(u).before('<img style="vertical-align: middle;" src="//img.atcoder.jp/assets/icon/crown_bronze.png">&nbsp;');
  54. else if (rating > 0) {
  55. const color = getColor(rating);
  56. const achRate = getAchRate(rating);
  57. $(u).before(`
  58. <span style="
  59. display: inline-block;
  60. height: 12px;
  61. width: 12px;
  62. vertical-align: center;
  63. border-radius: 50%;
  64. border: solid 1px ${color};
  65. background: -webkit-linear-gradient(
  66. bottom,
  67. ${color} 0%,
  68. ${color} ${achRate}%,
  69. rgba(255, 255, 255, 0.0) ${achRate}%,
  70. rgba(255, 255, 255, 0.0) 100%);
  71. "></span>
  72. `);
  73. }
  74. $(u).addClass(getColorClass(rating));
  75. }
  76.  
  77. function getRankingMap() {
  78. return new Promise(function(callback) {
  79. const currentTime = new Date().getTime();
  80. const lastUpdateTime = localStorage.getItem(lastUpdateKey);
  81. // Update every 3 hours
  82. if (lastUpdateTime && currentTime < Number(lastUpdateTime) + 3 * 60 * 60 * 1000) {
  83. callback(JSON.parse(localStorage.getItem(rankingKey)));
  84. } else {
  85. let ranking = {};
  86. $.ajax({
  87. url: "https://atcoder.jp/ranking",
  88. type: 'GET',
  89. dataType: 'html'
  90. })
  91. .done(function(data) {
  92. $($.parseHTML(data)).find('.username > span').each(function(idx) {
  93. const userName = $(this).text();
  94. ranking[userName] = idx + 1;
  95. });
  96. })
  97. .then(function() {
  98. localStorage.setItem(lastUpdateKey, currentTime);
  99. localStorage.setItem(rankingKey, JSON.stringify(ranking));
  100. callback(ranking);
  101. });
  102. }
  103. });
  104. }
  105.  
  106. function getRanking(rankingMap, userName) {
  107. if (userName in rankingMap) return rankingMap[userName];
  108. return OUT_OF_RANK;
  109. }
  110.  
  111. lscache.flushExpired();
  112. getRankingMap().then((rankingMap) => {
  113. let index = 0;
  114. $('a[href*="/users"]').each(function(_, u) {
  115. // Skip "My Profile"
  116. if ($(u).find('span').length) return true;
  117. const partUri = $(this).attr('href');
  118. const userName = partUri.slice(7);
  119. const lskey = "rating-" + userName;
  120. const ranking = getRanking(rankingMap, userName);
  121. let rating = lscache.get(lskey);
  122.  
  123. if (rating !== null) {
  124. colorize(u, ranking, rating);
  125. return;
  126. }
  127.  
  128. index += 1;
  129. setTimeout(function() {
  130. $.ajax({
  131. url: "https://atcoder.jp" + partUri + "/history/json",
  132. type: 'GET',
  133. dataType: 'json'
  134. })
  135. .done(function(data) {
  136. const ratedCount = data.length;
  137. if (ratedCount == 0) {
  138. rating = 0;
  139. } else {
  140. rating = data[ratedCount - 1]["NewRating"];
  141. }
  142. // Update every 3 hours
  143. lscache.set(lskey, rating, 3 * 60);
  144. })
  145. .then(function() {
  146. colorize(u, ranking, rating);
  147. });
  148. }, index * 300);
  149. });
  150. });
  151.  
  152. });