├── ???.js ├── README.md └── userscript.js /???.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name ioihw2020 做题工具 3 | // @namespace https://ioihw2020.duck-ac.cn 4 | // @version 0.1 5 | // @description 我啥时候也进个集训队啊 6 | // @author memset0 7 | // @match https://ioihw20.duck-ac.cn/ 8 | // @match https://ioihw20.duck-ac.cn/* 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | const colors = [ 13 | 'black', 14 | 'green', 15 | 'yellow', 16 | 'red', 17 | ]; 18 | 19 | const userlist = [ 20 | "虞皓翔", 21 | "马耀华", 22 | "彭博", 23 | "屠学畅", 24 | "黄子宽", 25 | "彭思进", 26 | "胡昊", 27 | "邓明扬", 28 | "周欣", 29 | "陈雨昕", 30 | "叶卓睿", 31 | "衍芃", 32 | "林昊翰", 33 | "李白天", 34 | "代晨昕", 35 | "张隽恺", 36 | "徐哲安", 37 | "郭城志", 38 | "徐舟子", 39 | "周镇东", 40 | "张好风", 41 | "袁浩天", 42 | "魏辰轩", 43 | "邱天异", 44 | "张博为", 45 | "陈峻宇", 46 | "孙诺舟", 47 | "蒋凌宇", 48 | "潘佳奇", 49 | "钱易", 50 | "张庭川", 51 | "丁晓漫", 52 | "左骏驰", 53 | "万天航", 54 | "施良致", 55 | "刘宇豪", 56 | "李泽清", 57 | "林立", 58 | "戴傅聪", 59 | "王泽远", 60 | "陈胤戬", 61 | "陆宏", 62 | "吕秋实", 63 | "欧阳宇鹏", 64 | "张记僖", 65 | "吴孟周", 66 | "曹原", 67 | "陈亮舟", 68 | "卢宸昊", 69 | "曾庆之", 70 | "万成章", 71 | "张景行", 72 | "戴江齐", 73 | "郑路明", 74 | "周航锐", 75 | "曹越", 76 | "冯施源", 77 | "罗恺", 78 | "冷滟泽", 79 | "杨珖", 80 | "陶立宇", 81 | "陈于思", 82 | "王相文", 83 | "孙嘉伟", 84 | "孙若凡", 85 | "宣毅鸣", 86 | "谢濡键", 87 | "孙从博", 88 | "许庭强", 89 | "周子衡", 90 | "苏焜", 91 | "管晏如", 92 | "陈永志", 93 | "蔡欣然", 94 | "韩豫葳", 95 | "张湫阳", 96 | "丁其安", 97 | "翁伟捷", 98 | "吴家庆", 99 | "潘逸飞", 100 | "谢琳涵", 101 | ]; 102 | 103 | const db = { 104 | load() { 105 | return JSON.parse(localStorage.getItem('hw') || '[]'); 106 | }, 107 | dump(data) { 108 | localStorage.setItem('hw', JSON.stringify(data || [])); 109 | }, 110 | update(pid, status) { 111 | let data = db.load(); 112 | data[pid] = status; 113 | db.dump(data); 114 | }, 115 | query(pid) { 116 | return db.load()[pid] || 0; 117 | }, 118 | }; 119 | 120 | function render() { 121 | $('*').each(function() { 122 | if (this.innerHTML.match(/^ioi2021_[0-9]+$/g)) { 123 | let uid = parseInt(this.innerHTML.match(/ioi2021_[0-9]+/g)[0].slice(8)); 124 | let name = userlist[uid]; 125 | if (name) { 126 | console.log(uid, name); 127 | this.innerHTML = '' + name + ''; 128 | } 129 | } 130 | }); 131 | 132 | if (location.pathname.startsWith('/problems')) { 133 | $(".table tr td:first-child").each(function () { 134 | let pid = this.innerHTML.slice(1); 135 | let status = db.query(pid); 136 | this.style.color = colors[status]; 137 | if (status) { 138 | this.style['font-weight'] = 'bold'; 139 | } else { 140 | this.style['font-weight'] = 'normal'; 141 | } 142 | console.log(pid, status); 143 | }); 144 | } 145 | } 146 | 147 | if (location.pathname.startsWith('/problems')) { 148 | $(".table tr td:first-child").click(function () { 149 | let pid = this.innerHTML.slice(1); 150 | let status = db.query(pid); 151 | status = (status + 1) % colors.length; 152 | db.update(pid, status); 153 | render(); 154 | }); 155 | } 156 | 157 | render(); 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ioihw20-helper 2 | 3 | ### Usage 4 | 5 | 1. 下载浏览器插件 Tamper Monkey,新建脚本。 6 | 2. 复制 [`./userscript.js`](https://raw.githubusercontent.com/memset0/ioihw20-helper/master/userscript.js) 的全部内容到新建脚本内,并保存。 7 | 3. **点亮右上角的 Star** 8 | 9 | ### Feature 10 | 11 | * 替换用户名成真实姓名。 12 | * 通过题数排行榜。 13 | * 显示卷王。 14 | * 从 [xzz 那里](https://github.com/xzz-233/ioihw20-problem) 抄了个显示原题的功能过来。 15 | * 好像没了。 16 | 17 | ### Preview 18 | 19 | ![](https://i.loli.net/2020/10/22/tfaoivbWOHDP3kJ.png) 20 | -------------------------------------------------------------------------------- /userscript.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name ioihw2020 做题工具 3 | // @namespace https://ioihw2020.duck-ac.cn 4 | // @version 0.5.8 5 | // @description 我啥时候也进个集训队啊 6 | // @author memset0 7 | // @match https://ioihw20.duck-ac.cn/ 8 | // @match https://ioihw20.duck-ac.cn/* 9 | // @updateURL https://cdn.jsdelivr.net/gh/memset0/ioihw20-helper@master/userscript.js 10 | // @downloadURL https://cdn.jsdelivr.net/gh/memset0/ioihw20-helper@master/userscript.js 11 | // @supportURL https://github.com/memset0/ioihw20-helper/issues 12 | // @homepage https://github.com/memset0/ioihw20-helper 13 | // @grant none 14 | // ==/UserScript== 15 | 16 | const config = { 17 | url: { 18 | codeforces: 'https://www.codeforces.com', 19 | }, 20 | }; 21 | 22 | const userlist = [ 23 | "虞皓翔", 24 | "马耀华", 25 | "彭博", 26 | "屠学畅", 27 | "黄子宽", 28 | "彭思进", 29 | "胡昊", 30 | "邓明扬", 31 | "周欣", 32 | "陈雨昕", 33 | "叶卓睿", 34 | "魏衍芃", 35 | "林昊翰", 36 | "李白天", 37 | "代晨昕", 38 | "张隽恺", 39 | "徐哲安", 40 | "郭城志", 41 | "徐舟子", 42 | "周镇东", 43 | "张好风", 44 | "袁浩天", 45 | "魏辰轩", 46 | "邱天异", 47 | "张博为", 48 | "陈峻宇", 49 | "孙诺舟", 50 | "蒋凌宇", 51 | "潘佳奇", 52 | "钱易", 53 | "张庭川", 54 | "丁晓漫", 55 | "左骏驰", 56 | "万天航", 57 | "施良致", 58 | "刘宇豪", 59 | "李泽清", 60 | "林立", 61 | "戴傅聪", 62 | "王泽远", 63 | "陈胤戬", 64 | "陆宏", 65 | "吕秋实", 66 | "欧阳宇鹏", 67 | "张记僖", 68 | "吴孟周", 69 | "曹原", 70 | "陈亮舟", 71 | "卢宸昊", 72 | "曾庆之", 73 | "万成章", 74 | "张景行", 75 | "戴江齐", 76 | "郑路明", 77 | "周航锐", 78 | "曹越", 79 | "冯施源", 80 | "罗恺", 81 | "冷滟泽", 82 | "杨珖", 83 | "陶立宇", 84 | "陈于思", 85 | "王相文", 86 | "孙嘉伟", 87 | "孙若凡", 88 | "宣毅鸣", 89 | "谢濡键", 90 | "孙从博", 91 | "许庭强", 92 | "周子衡", 93 | "苏焜", 94 | "管晏如", 95 | "陈永志", 96 | "蔡欣然", 97 | "韩豫葳", 98 | "张湫阳", 99 | "丁其安", 100 | "翁伟捷", 101 | "吴家庆", 102 | "潘逸飞", 103 | "谢琳涵", 104 | ]; 105 | 106 | const problemShortcutList = [ 107 | 'UC', 'DG', 'RB', 108 | 'HC', 'IB', 'FJ', 109 | 'GK', 'UH', 'QH', 110 | 'EE', 'DJ', 'IH', 111 | 'HL', 'MI', 'EJ', 112 | 'MB', 'UI', 'ED', 113 | 'AA', 'BG', 'ML', 114 | 'CF', 'TE', 'QG', 115 | 'PH', 'UJ', 'BB', 116 | 'CI', 'SD', 'NG', 117 | 'DK', 'LD', 'IJ', 118 | 'NC', 'BJ', 'FK', 119 | 'CH', 'NJ', 'RH', 120 | 'QF', 'BE', 'KI', 121 | 'AC', 'PG', 'HM', 122 | 'PJ', 'ID', 'EG', 123 | 'SI', 'KC', 'GI', 124 | 'OG', 'CK', 'DB', 125 | 'QE', 'GH', 'NI', 126 | 'OL', 'KA', 'MG', 127 | 'SG', 'NL', 'KH', 128 | 'QJ', 'KG', 'AB', 129 | 'KD', 'IL', 'NF', 130 | 'CM', 'NE', 'HD', 131 | 'DH', 'EC', 'BM', 132 | 'LC', 'CD', 'JI', 133 | 'DL', 'ME', 'PE', 134 | 'LI', 'AI', 'RJ', 135 | 'SF', 'II', 'HG', 136 | 'RE', 'LL', 'OA', 137 | 'FB', 'QD', 'DA', 138 | 'TC', 'AE', 'CB', 139 | 'GF', 'AG', 'JC', 140 | 'PA', 'TH', 'EH', 141 | 'AJ', 'TK', 'EI', 142 | 'UL', 'AF', 'SK', 143 | 'BH', 'RD', 'OK', 144 | 'FC', 'JD', 'OE', 145 | 'TF', 'FF', 'DD', 146 | 'CJ', 'HK', 'MJ', 147 | 'FG', 'GL', 'AL', 148 | 'FI', 'IC', 'QC', 149 | 'MD', 'OJ', 'HI', 150 | 'KK', 'JH', 'OB', 151 | 'BK', 'GJ', 'KE', 152 | 'CA', 'IK', 'LE', 153 | 'RI', 'LK', 'PD', 154 | 'JE', 'LB', 'HB', 155 | 'BL', 'RG', 'AH', 156 | 'AK', 'GG', 'JG', 157 | ]; 158 | 159 | const problemSourceIdList = [ 160 | 101221, 101239, 101242, 101471, 102482, 102511, 101630, 161 | 101190, 100851, 100553, 100307, 101620, 101173, 101480, 162 | 100543, 100299, 101612, 101142, 100801, 100531, 100269 163 | ]; 164 | 165 | const colors = [ 166 | 'black', 167 | 'green', 168 | 'yellow', 169 | 'red', 170 | ]; 171 | 172 | const db = { 173 | load() { 174 | return JSON.parse(localStorage.getItem('hw') || '[]'); 175 | }, 176 | dump(data) { 177 | localStorage.setItem('hw', JSON.stringify(data || [])); 178 | }, 179 | update(pid, status) { 180 | let data = db.load(); 181 | data[pid] = status; 182 | db.dump(data); 183 | }, 184 | query(pid) { 185 | return db.load()[pid] || 0; 186 | }, 187 | }; 188 | 189 | const dbWinner = { 190 | update(winner) { 191 | localStorage.setItem('hw-winner', String(winner)); 192 | }, 193 | query() { 194 | let plain = localStorage.getItem('hw-winner'); 195 | if (isNaN(parseInt(plain))) { 196 | return -1; 197 | } else { 198 | return parseInt(plain); 199 | } 200 | } 201 | }; 202 | 203 | function getProblemInfo(problemId) { 204 | let problemType = problemId == 1 ? '测试题' : (problemId < 300 && problemId % 4 ? '作业题' : '自选题'); 205 | 206 | let shortcut, shortcutId, contestId 207 | if (problemId >= 101) { 208 | shortcutId = problemId - 101 - ((problemId - 101) >> 2); 209 | shortcut = problemShortcutList[shortcutId]; 210 | contestId = problemType == '作业题' ? problemSourceIdList[shortcut.charCodeAt(0) - 65] : -1; 211 | } 212 | 213 | let authorId, authorName; 214 | if (problemId == 1) authorName = 'root'; 215 | else { 216 | authorId = (problemId - 101) >> 2; 217 | authorName = 'ioi2021_' + (problemId < 300 ? (authorId < 10 ? '0' : '') + String(authorId) : problemId - 300 + 49 ); 218 | } 219 | 220 | return { 221 | problemType, 222 | authorId, 223 | authorName, 224 | shortcut, 225 | shortcutId, 226 | contestId 227 | }; 228 | } 229 | 230 | function getUserInfo(id) { 231 | function strMatch(source, reg_exp, default_value) { 232 | let match_result = source.match(reg_exp); 233 | if (match_result) { 234 | return match_result[1]; 235 | } else { 236 | console.log("[Warning] string doesn't match!"); 237 | return default_value; 238 | } 239 | } 240 | id = id < 10 ? '0' + String(id) : String(id); 241 | return $.get({ 242 | url: `https://ioihw20.duck-ac.cn/user/profile/ioi2021_${id}`, 243 | }).then((res) => { 244 | let motto = strMatch(res, /

格言<\/h4>\s+

(.*?)<\/p>/s, ""); 245 | 246 | let regex = /"\/problem\/(\d+)"/g, match, count = 0; 247 | while (match = regex.exec(res)) { 248 | let problemId = parseInt(match[1]); 249 | let { problemType } = getProblemInfo(problemId); 250 | count += problemType == '作业题'; 251 | } 252 | 253 | return { 254 | id, 255 | motto, 256 | count, 257 | }; 258 | }); 259 | } 260 | 261 | async function render() { 262 | $('*').each(function () { 263 | if (this.innerHTML.match(/^ioi2021_[0-9]+$/g)) { 264 | let uid = parseInt(this.innerHTML.match(/ioi2021_[0-9]+/g)[0].slice(8)); 265 | let name = userlist[uid]; 266 | if (uid == dbWinner.query()) { 267 | name += '卷王'; 268 | } 269 | if (name) { 270 | console.log(uid, name); 271 | this.innerHTML = '' + name + ''; 272 | } 273 | } 274 | }); 275 | 276 | if (location.pathname.startsWith('/problems')) { 277 | $(".table tr td:first-child").each(function () { 278 | let pid = this.innerHTML.slice(1); 279 | let status = db.query(pid); 280 | this.style.color = colors[status]; 281 | if (status) { 282 | this.style['font-weight'] = 'bold'; 283 | } else { 284 | this.style['font-weight'] = 'normal'; 285 | } 286 | console.log(pid, status); 287 | }); 288 | } 289 | } 290 | 291 | async function mainRender() { 292 | $('.navbar .navbar-nav').append('

  • 排行榜
  • ') 293 | 294 | if (location.pathname == '/ranklist') { 295 | document.title = document.title.replace('比赛排行榜', '排行榜'); 296 | 297 | let userListPromised = []; 298 | $('.pagination').remove(); 299 | $('.table tbody tr').remove(); 300 | for (let userId = 0; userId < 81; userId++) { 301 | userListPromised.push(getUserInfo(userId)); 302 | } 303 | let userList = await Promise.all(userListPromised); 304 | userList.sort((firstUser, secondUser) => { 305 | console.log(firstUser, secondUser); 306 | if (firstUser.count == secondUser.count) { 307 | return parseInt(firstUser.id) - parseInt(secondUser.id); 308 | } 309 | return secondUser.count - firstUser.count; 310 | }); 311 | if (userList.length) { 312 | dbWinner.update(userList[0].id); 313 | } 314 | console.log(userList); 315 | $('.table thead tr th:last-child').text('通过数'); 316 | for (let user of userList) { 317 | console.log(user); 318 | let $tr = $(''); 319 | $tr.append(`${user.id}`); 320 | $tr.append(`ioi2021_${user.id}`); 321 | $tr.append(`${user.motto}`); 322 | $tr.append(`${user.count}`); 323 | $('.table tbody').append($tr); 324 | } 325 | } 326 | 327 | if (location.pathname == '/problems' || location.pathname.startsWith('/problems/')) { 328 | $('.table thead tr th:last-child').css('width', '170px'); 329 | $('.table thead tr').eq(0).append('来源'); 330 | $('.table tbody tr').each(function(index, element) { 331 | let $element = $(element); 332 | let problemId = $element.children('td').eq(0).text().slice(1); 333 | let { problemType, shortcut, contestId } = getProblemInfo(problemId); 334 | 335 | let extraContent = ''; 336 | if (problemType == '作业题') { 337 | extraContent = ` 338 | 339 | ${shortcut} 340 | 341 | 342 | (${contestId}) 343 | 344 | `; 345 | } 346 | 347 | $element.append('' + extraContent + ''); 348 | }); 349 | } 350 | 351 | if (location.pathname.startsWith('/problem/')) { 352 | let problemId = parseInt(location.href.substr(location.href.lastIndexOf("problem/") + 8, 3), 10); 353 | let { problemType, authorName, shortcut, shortcutId, contestId } = getProblemInfo(problemId); 354 | 355 | $(".nav-tabs").eq(0).append(`
  • 356 | 357 | ${problemType} 358 | by 359 | 360 | ${authorName} 361 | 362 | 363 |
  • `); 364 | 365 | if (problemType == '作业题') { 366 | $(".nav-tabs").eq(0).append(`
  • 367 | 368 | Source:   369 | 370 | ${shortcut} 371 | 372 | 373 | (${contestId}) 374 | 375 | 376 |
  • `); 377 | } 378 | } 379 | 380 | render(); 381 | } 382 | 383 | if (location.pathname.startsWith('/problems')) { 384 | $(".table tr td:first-child").click(function () { 385 | let pid = this.innerHTML.slice(1); 386 | let status = db.query(pid); 387 | status = (status + 1) % colors.length; 388 | db.update(pid, status); 389 | render(); 390 | }); 391 | } 392 | 393 | mainRender(); 394 | --------------------------------------------------------------------------------