├── README.md
├── img
├── submission_all.png
└── submission_indiv.png
└── main.js
/README.md:
--------------------------------------------------------------------------------
1 | # AtCoderSubmissionUserColorizer
2 |
3 | [](https://greasyfork.org/ja/scripts/397710-atcoder-submission-user-colorizer)
4 |
5 | AtCoderの提出一覧のユーザ名を色付けするUserScript.
6 |
7 | 
8 | 
9 |
10 | ## License
11 |
12 | These codes are licensed under CC0.
13 |
14 | [](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 |
--------------------------------------------------------------------------------