├── README.md ├── img ├── submission_all.png └── submission_indiv.png └── main.js /README.md: -------------------------------------------------------------------------------- 1 | # AtCoderSubmissionUserColorizer 2 | 3 | [![GreasyFork](https://img.shields.io/badge/GreasyFork-install-orange)](https://greasyfork.org/ja/scripts/397710-atcoder-submission-user-colorizer) 4 | 5 | AtCoderの提出一覧のユーザ名を色付けするUserScript. 6 | 7 | ![個別の提出](./img/submission_indiv.png) 8 | ![提出一覧](./img/submission_all.png) 9 | 10 | ## License 11 | 12 | These codes are licensed under CC0. 13 | 14 | [![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png "CC0")](http://creativecommons.org/publicdomain/zero/1.0/deed.ja) 15 | -------------------------------------------------------------------------------- /img/submission_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morioprog/AtCoderSubmissionUserColorizer/af1d00f49f2b32ecdf8c693c92a373a799e78118/img/submission_all.png -------------------------------------------------------------------------------- /img/submission_indiv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morioprog/AtCoderSubmissionUserColorizer/af1d00f49f2b32ecdf8c693c92a373a799e78118/img/submission_indiv.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AtCoder Submission User Colorizer 3 | // @namespace https://github.com/morioprog 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(' '); 51 | else if (ranking <= 10) $(u).before(' '); 52 | else if (ranking <= 30) $(u).before(' '); 53 | else if (ranking <= 100) $(u).before(' '); 54 | else if (rating > 0) { 55 | const color = getColor(rating); 56 | const achRate = getAchRate(rating); 57 | $(u).before(` 58 | 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 | }); 153 | --------------------------------------------------------------------------------