├── .eslintrc.json ├── req.js ├── genCountry.js ├── base64.js ├── README.md └── scoreboard.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "google", 3 | "env": { 4 | "es6": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /req.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const https = require('https'); 4 | 5 | module.exports.get = function(url, cb) { 6 | https.get(url, (res) => { 7 | if (res.statusCode < 200 || res.statusCode >= 300) { 8 | cb(Error('HTTP ' + res.statusCode)); 9 | return; 10 | } 11 | let data = ''; 12 | res.on('data', (chunk) => { 13 | data += chunk; 14 | }); 15 | res.on('end', () => { 16 | cb(null, data); 17 | }); 18 | }).on('error', (err) => { 19 | cb(err); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /genCountry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const base64 = require('./base64'); 4 | const req = require('./req'); 5 | 6 | const baseUrl = 'https://codejam.googleapis.com/'; 7 | const getP = (from, cnt) => { 8 | const r = { 9 | min_rank: from, 10 | num_consecutive_users: cnt, 11 | }; 12 | const n = base64.encodeURI(JSON.stringify(r)); 13 | return n; 14 | }; 15 | 16 | const contestIds = [ 17 | '0000000000000130', 18 | '00000000000000cb', 19 | '0000000000007883', 20 | '0000000000007764', 21 | '0000000000007765', 22 | '0000000000007706', 23 | '0000000000007707', 24 | '0000000000051705', 25 | '0000000000051635', 26 | '0000000000051706', 27 | '00000000000516b9', 28 | '0000000000051679', 29 | ]; 30 | 31 | const outerPromises = []; 32 | for (const contestId of contestIds) { 33 | const scoreboardUrl = baseUrl + 'scoreboard/' + contestId + '/poll?p=' + 34 | getP(1, 1); 35 | outerPromises.push(new Promise((outerResolve) => { 36 | req.get(scoreboardUrl, (err, res) => { 37 | if (err) { 38 | console.log(err.message); 39 | return; 40 | } 41 | const data = JSON.parse(base64.decode(res)); 42 | const tot = data.full_scoreboard_size; 43 | let at = 0; 44 | const promises = []; 45 | while (at < tot) { 46 | const cnt = Math.min(200, tot - at); 47 | const scoreboardUrl = baseUrl + 'scoreboard/' + contestId + '/poll?p=' + 48 | getP(at + 1, cnt); 49 | at += cnt; 50 | promises.push(new Promise((resolve) => { 51 | req.get(scoreboardUrl, (err, res) => { 52 | if (err) { 53 | console.log(err.message); 54 | resolve([]); 55 | return; 56 | } 57 | const data = JSON.parse(base64.decode(res)); 58 | resolve(data.user_scores); 59 | }); 60 | })); 61 | } 62 | Promise.all(promises).then((value) => { 63 | const data = [].concat(...value); 64 | outerResolve(data); 65 | }); 66 | }); 67 | })); 68 | } 69 | 70 | let countries = new Set(); 71 | Promise.all(outerPromises).then((value) => { 72 | const data = [].concat(...value); 73 | for (const user of data) { 74 | if (user.country !== '') { 75 | countries.add(user.country); 76 | } 77 | } 78 | countries = [...countries]; 79 | countries.sort(); 80 | for (const country of countries) { 81 | console.log('*', country); 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /base64.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const u = String.fromCharCode; 4 | const c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 5 | const l = function(e) { 6 | const t = {}; 7 | const i = e.length; 8 | for (let n = 0; n < i; n++) { 9 | t[e.charAt(n)] = n; 10 | } 11 | return t; 12 | }(c); 13 | const b = new RegExp(['[À-ß][€-¿]", "[à-ï][€-¿]{2}", "[ð-÷][€-¿]{3}'].join('|'), 14 | 'g'); 15 | const w = function(e) { 16 | switch (e.length) { 17 | case 4: 18 | const t = (7 & e.charCodeAt(0)) << 18 | (63 & e.charCodeAt(1)) << 12 | 19 | (63 & e.charCodeAt(2)) << 6 | 63 & e.charCodeAt(3); 20 | const n = t - 65536; 21 | return u(55296 + (n >>> 10)) + u(56320 + (1023 & n)); 22 | case 3: 23 | return u((15 & e.charCodeAt(0)) << 12 | (63 & e.charCodeAt(1)) << 6 | 24 | 63 & e.charCodeAt(2)); 25 | default: 26 | return u((31 & e.charCodeAt(0)) << 6 | 63 & e.charCodeAt(1)); 27 | } 28 | }; 29 | const k = function(e) { 30 | return e.replace(b, w); 31 | }; 32 | const x = function(e) { 33 | const t = e.length; 34 | const n = t % 4; 35 | const i = (t > 0 ? l[e.charAt(0)] << 18 : 0) | 36 | (t > 1 ? l[e.charAt(1)] << 12 : 0) | 37 | (t > 2 ? l[e.charAt(2)] << 6 : 0) | (t > 3 ? l[e.charAt(3)] : 0); 38 | const r = [u(i >>> 16), u(i >>> 8 & 255), u(255 & i)]; 39 | r.length -= [0, 0, 2, 1][n]; 40 | return r.join(''); 41 | }; 42 | const _ = function(e) { 43 | return e.replace(/[\s\S]{1,4}/g, x); 44 | }; 45 | const a = function(e) { 46 | return k(_(e)); 47 | }; 48 | module.exports.decode = function(e) { 49 | return a(String(e).replace(/[-_]/g, function(e) { 50 | return '-' == e ? '+' : '/'; 51 | }).replace(/[^A-Za-z0-9\+\/]/g, '')); 52 | }; 53 | 54 | const h = function(e) { 55 | if (e.length < 2) { 56 | const t = e.charCodeAt(0); 57 | return t < 128 ? e : t < 2048 ? u(192 | t >>> 6) + u(128 | 63 & t) : 58 | u(224 | t >>> 12 & 15) + 59 | u(128 | t >>> 6 & 63) + u(128 | 63 & t); 60 | } 61 | const t = 65536 + 1024 * (e.charCodeAt(0) - 55296) + 62 | (e.charCodeAt(1) - 56320); 63 | return u(240 | t >>> 18 & 7) + u(128 | t >>> 12 & 63) + 64 | u(128 | t >>> 6 & 63) + u(128 | 63 & t); 65 | }; 66 | const p = function(e) { 67 | const t = [0, 2, 1][e.length % 3]; 68 | const n = e.charCodeAt(0) << 16 | (e.length > 1 ? e.charCodeAt(1) : 0) << 8 | 69 | (e.length > 2 ? e.charCodeAt(2) : 0); 70 | return [ 71 | c.charAt(n >>> 18), 72 | c.charAt(n >>> 12 & 63), 73 | t >= 2 ? '=' : c.charAt(n >>> 6 & 63), 74 | t >= 1 ? '=' : c.charAt(63 & n), 75 | ].join(''); 76 | }; 77 | const g = function(e) { 78 | return e.replace(/[\s\S]{1,3}/g, p); 79 | }; 80 | const d = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; 81 | const f = function(e) { 82 | return e.replace(d, h); 83 | }; 84 | const m = function(e) { 85 | return g(f(e)); 86 | }; 87 | const v = function(e, t) { 88 | return t ? m(String(e)).replace(/[+\/]/g, function(e) { 89 | return '+' == e ? '-' : '_'; 90 | }).replace(/=/g, '') : m(String(e)); 91 | }; 92 | module.exports.encodeURI = function(e) { 93 | return v(e, !0); 94 | }; 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GCJ 2018 scoreboard API 2 | 3 | To interact with GCJ 2018 scoreboard API 4 | 5 | ## Usage 6 | 7 | Run `node scoreboard contestID [country]` 8 | 9 | ## contestID 10 | 11 | * 2018 Practice Session : `0000000000000130` 12 | * 2018 Qualification Round: `00000000000000cb` 13 | * 2018 Round 1A: `0000000000007883` 14 | * 2018 Round 1B: `0000000000007764` 15 | * 2018 Round 1C: `0000000000007765` 16 | * 2018 Round 2: `0000000000007706` 17 | * 2018 Round 3: `0000000000007707` 18 | * 2019 Qualification Round: `0000000000051705` 19 | * 2019 Round 1A: `0000000000051635` 20 | * 2019 Round 1B: `0000000000051706` 21 | * 2019 Round 1C: `00000000000516b9` 22 | * 2019 Round 2: `0000000000051679` 23 | * 2019 Round 3: `0000000000051707` 24 | 25 | ## country 26 | 27 | * Afghanistan 28 | * Albania 29 | * Algeria 30 | * American Samoa 31 | * Andorra 32 | * Angola 33 | * Anguilla 34 | * Antarctica 35 | * Argentina 36 | * Armenia 37 | * Australia 38 | * Austria 39 | * Azerbaijan 40 | * Bahrain 41 | * Bangladesh 42 | * Belarus 43 | * Belgium 44 | * Benin 45 | * Bhutan 46 | * Bolivia 47 | * Bosnia and Herzegovina 48 | * Botswana 49 | * Brazil 50 | * British Indian Ocean Territory 51 | * British Virgin Islands 52 | * Brunei 53 | * Bulgaria 54 | * Burkina Faso 55 | * Burundi 56 | * Cambodia 57 | * Cameroon 58 | * Canada 59 | * Cayman Islands 60 | * Chad 61 | * Chile 62 | * China 63 | * Colombia 64 | * Congo [DRC] 65 | * Congo [Republic] 66 | * Costa Rica 67 | * Croatia 68 | * Cuba 69 | * Cyprus 70 | * Czech Republic 71 | * Côte d'Ivoire 72 | * Denmark 73 | * Djibouti 74 | * Dominica 75 | * Dominican Republic 76 | * Ecuador 77 | * Egypt 78 | * El Salvador 79 | * Estonia 80 | * Ethiopia 81 | * Fiji 82 | * Finland 83 | * France 84 | * Gabon 85 | * Georgia 86 | * Germany 87 | * Ghana 88 | * Greece 89 | * Greenland 90 | * Guadeloupe 91 | * Guatemala 92 | * Guernsey 93 | * Guinea 94 | * Haiti 95 | * Honduras 96 | * Hong Kong 97 | * Hungary 98 | * Iceland 99 | * India 100 | * Indonesia 101 | * Iran 102 | * Iraq 103 | * Ireland 104 | * Isle of Man 105 | * Israel 106 | * Italy 107 | * Jamaica 108 | * Japan 109 | * Jersey 110 | * Jordan 111 | * Kazakhstan 112 | * Kenya 113 | * Kosovo 114 | * Kuwait 115 | * Kyrgyzstan 116 | * Laos 117 | * Latvia 118 | * Lebanon 119 | * Lesotho 120 | * Lithuania 121 | * Luxembourg 122 | * Macau 123 | * Macedonia [FYROM] 124 | * Madagascar 125 | * Malawi 126 | * Malaysia 127 | * Malta 128 | * Martinique 129 | * Mauritius 130 | * Mexico 131 | * Moldova 132 | * Monaco 133 | * Mongolia 134 | * Montenegro 135 | * Morocco 136 | * Mozambique 137 | * Myanmar [Burma] 138 | * Namibia 139 | * Nepal 140 | * Netherlands 141 | * New Zealand 142 | * Nicaragua 143 | * Niger 144 | * Nigeria 145 | * North Korea 146 | * Norway 147 | * Oman 148 | * Pakistan 149 | * Palestine 150 | * Panama 151 | * Paraguay 152 | * Peru 153 | * Philippines 154 | * Poland 155 | * Portugal 156 | * Puerto Rico 157 | * Qatar 158 | * Romania 159 | * Russia 160 | * Rwanda 161 | * Réunion 162 | * Saudi Arabia 163 | * Senegal 164 | * Serbia 165 | * Seychelles 166 | * Singapore 167 | * Sint Maarten 168 | * Slovakia 169 | * Slovenia 170 | * Somalia 171 | * South Africa 172 | * South Georgia and the South Sandwich Islands 173 | * South Korea 174 | * Spain 175 | * Sri Lanka 176 | * Sudan 177 | * Swaziland 178 | * Sweden 179 | * Switzerland 180 | * Syria 181 | * Taiwan 182 | * Tajikistan 183 | * Tanzania 184 | * Thailand 185 | * Togo 186 | * Tokelau 187 | * Trinidad and Tobago 188 | * Tunisia 189 | * Turkey 190 | * Turkmenistan 191 | * Turks and Caicos Islands 192 | * Tuvalu 193 | * U.S. Minor Outlying Islands 194 | * U.S. Virgin Islands 195 | * Uganda 196 | * Ukraine 197 | * United Arab Emirates 198 | * United Kingdom 199 | * United States 200 | * Uruguay 201 | * Uzbekistan 202 | * Vatican City 203 | * Venezuela 204 | * Vietnam 205 | * Western Sahara 206 | * Yemen 207 | * Zimbabwe 208 | * Åland Islands 209 | * Decline to Answer 210 | -------------------------------------------------------------------------------- /scoreboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const base64 = require('./base64'); 4 | const req = require('./req'); 5 | 6 | const baseUrl = 'https://codejam.googleapis.com/'; 7 | const getP = (from, cnt) => { 8 | const r = { 9 | min_rank: from, 10 | num_consecutive_users: cnt, 11 | }; 12 | const n = base64.encodeURI(JSON.stringify(r)); 13 | return n; 14 | }; 15 | const nSpace = (cnt) => { 16 | let ret = ''; 17 | for (let i = 0; i < cnt; i++) { 18 | ret += ' '; 19 | } 20 | return ret; 21 | }; 22 | const timeToString = (time) => { 23 | time -= time % 1000000; 24 | time /= 1000000; 25 | let sec = time % 60; 26 | time -= sec; 27 | time /= 60; 28 | sec = sec.toString(); 29 | if (sec.length == 1) { 30 | sec = '0' + sec; 31 | } 32 | let min = time % 60; 33 | time -= min; 34 | time /= 60; 35 | min = min.toString(); 36 | if (min.length == 1) { 37 | min = '0' + min; 38 | } 39 | time += ':' + min + ':' + sec; 40 | return time; 41 | }; 42 | 43 | const contestId = process.argv[2]; 44 | let country = ''; 45 | if (process.argv.length >= 4) { 46 | country = process.argv[3]; 47 | } 48 | 49 | // const dashboardUrl = baseUrl + 'dashboard/' + contestId + '/poll?p=e30'; 50 | const scoreboardUrl = baseUrl + 'scoreboard/' + contestId + '/poll?p=' + 51 | getP(1, 1); 52 | req.get(scoreboardUrl, (err, res) => { 53 | if (err) { 54 | console.log(err.message); 55 | return; 56 | } 57 | const data = JSON.parse(base64.decode(res)); 58 | console.log('===' + data.challenge.title + '==='); 59 | if ('additional_info' in data.challenge && 60 | data.challenge.additional_info.length > 0) { 61 | console.log(data.challenge.additional_info); 62 | } 63 | const tot = data.full_scoreboard_size; 64 | console.log('number of participants:', tot); 65 | console.log(); 66 | const size = [0, 0, 0, 0]; 67 | const taskMap = {}; 68 | const testCnt = []; 69 | for (let i = 0; i < data.challenge.tasks.length; i++) { 70 | const task = data.challenge.tasks[i]; 71 | taskMap[task.id] = i; 72 | testCnt.push(task.tests.length); 73 | console.log('{' + (i + 1) + '}', task.title); 74 | for (let j = 0; j < task.tests.length; j++) { 75 | const test = task.tests[j]; 76 | test.name = ' [' + (j + 1) + ']'; 77 | size[0] = Math.max(size[0], test.name.length); 78 | test.value += 'pt'; 79 | size[1] = Math.max(size[1], test.value.length); 80 | test.type__str = test.type__str.toLowerCase(); 81 | size[2] = Math.max(size[2], test.type__str.length); 82 | if ('num_solved' in test) { 83 | test.cnt = test.num_solved + '/' + task.num_attempted; 84 | } else { 85 | test.cnt = '?/' + task.num_attempted; 86 | } 87 | size[3] = Math.max(size[3], test.cnt.length); 88 | } 89 | for (let j = 0; j < task.tests.length; j++) { 90 | const test = task.tests[j]; 91 | let line = test.name; 92 | line += nSpace(size[0] - test.name.length); 93 | line += ' '; 94 | line += nSpace(size[1] - test.value.length); 95 | line += test.value; 96 | line += ' '; 97 | line += nSpace(size[2] - test.type__str.length); 98 | line += test.type__str; 99 | line += ' '; 100 | line += nSpace(size[3] - test.cnt.length); 101 | line += test.cnt; 102 | console.log(line); 103 | } 104 | } 105 | console.log(); 106 | let at = 0; 107 | const promises = []; 108 | const result = []; 109 | while (at < tot) { 110 | const cnt = Math.min(200, tot - at); 111 | const scoreboardUrl = baseUrl + 'scoreboard/' + contestId + '/poll?p=' + 112 | getP(at + 1, cnt); 113 | at += cnt; 114 | result.push([]); 115 | promises.push(new Promise((resolve) => { 116 | req.get(scoreboardUrl, (err, res) => { 117 | if (err) { 118 | console.log(err.message); 119 | resolve([]); 120 | return; 121 | } 122 | const data = JSON.parse(base64.decode(res)); 123 | if (country.length == 0) { 124 | resolve(data.user_scores); 125 | } else { 126 | const ret = []; 127 | for (let i = 0; i < data.user_scores.length; i++) { 128 | const user = data.user_scores[i]; 129 | if (user.country == country) { 130 | ret.push(user); 131 | } 132 | } 133 | resolve(ret); 134 | } 135 | }); 136 | })); 137 | } 138 | Promise.all(promises).then((value) => { 139 | const data = [].concat(...value); 140 | const size = [0, 0, 0, 0, 0]; 141 | for (let i = 0; i < Object.keys(taskMap).length; i++) { 142 | size.push(1); 143 | for (let j = 0; j < 3; j++) { 144 | size.push(0); 145 | } 146 | } 147 | for (let i = 0; i < data.length; i++) { 148 | const user = data[i]; 149 | user.rank = user.rank.toString(); 150 | size[0] = Math.max(size[0], user.rank.length); 151 | user.displayname = user.displayname.replace(/[^\x20-\x7E]+/g, '?'); 152 | size[1] = Math.max(size[1], user.displayname.length); 153 | size[2] = Math.max(size[2], user.country.length); 154 | user.score_1 = user.score_1 + 'pt'; 155 | size[3] = Math.max(size[3], user.score_1.length); 156 | user.score_2 = timeToString(-user.score_2); 157 | size[4] = Math.max(size[4], user.score_2.length); 158 | user.task = []; 159 | for (let j = 0; j < Object.keys(taskMap).length; j++) { 160 | user.task.push({}); 161 | } 162 | for (let j = 0; j < user.task_info.length; j++) { 163 | const task = user.task_info[j]; 164 | const ind = taskMap[task.task_id]; 165 | user.task[ind] = task; 166 | } 167 | let at = 5; 168 | for (let j = 0; j < Object.keys(taskMap).length; j++) { 169 | const task = user.task[j]; 170 | at++; 171 | if ('task_id' in task) { 172 | if (!('penalty_micros' in task) || task.penalty_micros < 0) { 173 | task.penalty_micros = '-'; 174 | } else { 175 | task.penalty_micros = timeToString(task.penalty_micros); 176 | } 177 | task.total_attempts += '/' + task.penalty_attempts; 178 | task.tests = ''; 179 | for (let k = 0; k < task.tests_definitely_solved; k++) { 180 | task.tests += 'A'; 181 | } 182 | for (let k = 0; k < task.tests_possibly_solved; k++) { 183 | task.tests += '?'; 184 | } 185 | while (task.tests.length != testCnt[j]) { 186 | task.tests += '_'; 187 | } 188 | } else { 189 | task.penalty_micros = '-'; 190 | task.total_attempts = '-'; 191 | task.tests = '-'; 192 | } 193 | size[at] = Math.max(size[at], task.penalty_micros.length); 194 | at++; 195 | size[at] = Math.max(size[at], task.total_attempts.length); 196 | at++; 197 | size[at] = Math.max(size[at], task.tests.length); 198 | at++; 199 | } 200 | } 201 | for (let i = 0; i < data.length; i++) { 202 | const user = data[i]; 203 | let line = user.rank; 204 | line += nSpace(size[0] - user.rank.length); 205 | line += ' '; 206 | line += nSpace(size[1] - user.displayname.length); 207 | line += user.displayname; 208 | line += ' '; 209 | line += nSpace(size[2] - user.country.length); 210 | line += user.country; 211 | line += ' '; 212 | line += nSpace(size[3] - user.score_1.length); 213 | line += user.score_1; 214 | line += ' '; 215 | line += nSpace(size[4] - user.score_2.length); 216 | line += user.score_2; 217 | let at = 5; 218 | for (let j = 0; j < Object.keys(taskMap).length; j++) { 219 | const task = user.task[j]; 220 | line += ' | '; 221 | at++; 222 | line += nSpace(size[at] - task.penalty_micros.length); 223 | line += task.penalty_micros; 224 | at++; 225 | line += ' '; 226 | line += nSpace(size[at] - task.total_attempts.length); 227 | line += task.total_attempts; 228 | at++; 229 | line += ' '; 230 | line += nSpace(size[at] - task.tests.length); 231 | line += task.tests; 232 | at++; 233 | } 234 | console.log(line); 235 | } 236 | }); 237 | }); 238 | --------------------------------------------------------------------------------