Last active
June 14, 2020 09:14
-
-
Save cielavenir/c012d330a25f358eff2e911c1b5d5a5b to your computer and use it in GitHub Desktop.
ac-DarkDebugger
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name ac-DarkDebugger | |
// @namespace https://github.com/my316g/ | |
// @supportURL https://twitter.com/my316g/ | |
// @version 0.2.2 | |
// @description 1WA に泣く人のために | |
// @author my316g | |
// @match https://atcoder.jp/contests/*/submissions/* | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// language ids | |
let dict_language = { | |
"C++14 (GCC 5.4.1)":"3003","Bash (GNU bash v4.3.11)":"3001","C (GCC 5.4.1)":"3002","C (Clang 3.8.0)":"3004","C++14 (Clang 3.8.0)":"3005", | |
"C# (Mono 4.6.2.0)":"3006","Clojure (1.8.0)":"3007","Common Lisp (SBCL 1.1.14)":"3008","D (DMD64 v2.070.1)":"3009","D (LDC 0.17.0)":"3010", | |
"D (GDC 4.9.4)":"3011","Fortran (gfortran v4.8.4)":"3012","Go (1.6)":"3013","Haskell (GHC 7.10.3)":"3014","Java7 (OpenJDK 1.7.0)":"3015", | |
"Java8 (OpenJDK 1.8.0)":"3016","JavaScript (node.js v5.12)":"3017","OCaml (4.02.3)":"3018","Pascal (FPC 2.6.2)":"3019","Perl (v5.18.2)":"3020", | |
"PHP (5.6.30)":"3021","Python2 (2.7.6)":"3022","Python3 (3.4.3)":"3023","Ruby (2.3.3)":"3024","Scala (2.11.7)":"3025","Scheme (Gauche 0.9.3.3)":"3026", | |
"Text (cat)":"3027","Visual Basic (Mono 4.0.1)":"3028","C++ (GCC 5.4.1)":"3029","C++ (Clang 3.8.0)":"3030","Objective-C (GCC 5.3.0)":"3501", | |
"Objective-C (Clang3.8.0)":"3502","Swift (swift-2.2-RELEASE)":"3503","Rust (1.15.1)":"3504","Sed (GNU sed 4.2.2)":"3505","Awk (mawk 1.3.3)":"3506", | |
"Brainfuck (bf 20041219)":"3507","Standard ML (MLton 20100608)":"3508","PyPy2 (5.6.0)":"3509","PyPy3 (2.4.0)":"3510","Crystal (0.20.5)":"3511", | |
"F# (Mono 4.0)":"3512","Unlambda (0.1.3)":"3513","Lua (5.3.2)":"3514","LuaJIT (2.0.4)":"3515","MoonScript (0.5.0)":"3516","Ceylon (1.2.1)":"3517", | |
"Julia (0.5.0)":"3518","Octave (4.0.2)":"3519","Nim (0.13.0)":"3520","TypeScript (2.1.6)":"3521","Perl6 (rakudo-star 2016.01)":"3522","Kotlin (1.0.0)":"3523", | |
"PHP7 (7.0.15)":"3524","COBOL - Fixed (OpenCOBOL 1.1.0)":"3525","COBOL - Free (OpenCOBOL 1.1.0)":"3526" | |
}; | |
// let selects = $("#select-language option"); | |
// for(let i = 0; i < selects.length; i++){ | |
// let language_name = selects[i]["text"]; | |
// let language_id = selects[i]["value"]; | |
// if(language_name == "") | |
// continue; | |
// dict_language[language_name] = language_id; | |
// } | |
// console.log(JSON.stringify(dict_language)); | |
let urlHome = location.href; | |
let regExp = /https\:\/\/atcoder\.jp\/contests\/.+\/submissions\/\d+/; | |
if(urlHome.search(regExp) == -1) | |
return; | |
while(true){ | |
let c = urlHome[urlHome.length - 1]; | |
if(c == '/'){ | |
urlHome = urlHome.slice(0, -1); | |
break; | |
} | |
else | |
urlHome = urlHome.slice(0, -1); | |
} | |
function getUserInfo(dst, str){ | |
str = str.split(/\"/g); | |
let str2 = []; | |
for(let s of str) | |
if(s[0] === '/') | |
str2.push(s); | |
str2[0] = str2[0].split('/')[2]; | |
dst["Username"] = str2[0]; | |
dst["UserSubmissionsURL"] = str2[1]; | |
} | |
function getTimeVal(str){ | |
let regExp = /[-\:\+ ]/g; | |
let ta = str.split('>')[1].split('<')[0].split(regExp).slice(0, 6).map(s => parseInt(s)); | |
let tn = ((((ta[0] * 12 + ta[1]) * 31 + ta[2]) * 24 + ta[3]) * 60 + ta[4]) * 60 + ta[5]; | |
return tn; | |
} | |
// 提出一覧ページの tr タグ内の提出情報を抽出する | |
function getStatusFromSubmissions(tr){ | |
let dict_submission = new Object(); | |
let tds = $(tr).find('td'); | |
let tdTime = $(tds).eq(0).find('time').prop('outerHTML'); | |
let tdProb = $(tds).eq(1).find('a').attr('href').split('\/'); | |
tdProb = tdProb[tdProb.length - 1]; | |
let tdUser = $(tds).eq(2).find('a'); | |
let tdUsername = $(tdUser).eq(0).attr('href').split('\/')[2]; | |
let tdUserSubmissionsURL = $(tdUser).eq(1).attr('href'); | |
let tdLang = $(tds).eq(3).html(); | |
let tdVerdict = $(tds).find('.label').text(); | |
let tdSubmissionURL = $(tds).last().find('a').attr('href'); | |
dict_submission['Time'] = getTimeVal(tdTime); | |
dict_submission['Task'] = tdProb; | |
dict_submission['Username'] = tdUsername; | |
dict_submission['UserSubmissionsURL'] = tdUserSubmissionsURL; | |
dict_submission['Language'] = dict_language[tdLang]; | |
dict_submission['Verdict'] = tdVerdict; | |
dict_submission['SubmissionURL'] = tdSubmissionURL; | |
return dict_submission; | |
} | |
function getStatusesFromSubmissions(html){ | |
let statuses = []; | |
let trs = $(html).find("tbody tr"); | |
for(let i = 0; i < $(trs).length; i++){ | |
statuses.push(getStatusFromSubmissions($(trs).eq(i))); | |
} | |
return statuses; | |
} | |
function getStatusFromSubmission(html) { | |
let dict_submission = new Object(); | |
let tbody = $(html).find("tbody"); | |
let trs = $(tbody[0]).find("tr"); | |
for(let i = 0; i < trs.length; i++){ | |
let th = $(trs[i]).find("th").html(); | |
let td = $(trs[i]).find("td").html(); | |
dict_submission[th] = td; | |
} | |
let dict_status = new Object(); | |
let verdicts = $(tbody[2]).find(".label").text(); | |
dict_status["Verdicts"] = verdicts; | |
let taskname = dict_submission["問題"]; | |
taskname = taskname.split(/[\/\"]/); | |
taskname = taskname[taskname.indexOf("tasks") + 1]; | |
dict_status["Task"] = taskname; | |
let userinfo = dict_submission["ユーザ"]; | |
let lang_id = dict_language[dict_submission["言語"]]; | |
dict_status["Language"] = lang_id; | |
let verdict = $(dict_submission["結果"]).text(); | |
dict_status["Verdict"] = verdict; | |
dict_status["Time"] = getTimeVal(dict_submission["提出日時"]); | |
getUserInfo(dict_status, userinfo); | |
return dict_status; | |
} | |
let $result; | |
let myStatus = getStatusFromSubmission($('html')); | |
let numPage = 0; | |
let maxPage; | |
// Verdict が AC や WJ なら必要ないので | |
if(myStatus['Verdict'] == 'AC' || myStatus['Verdict'] == 'WJ') | |
return; | |
let btnMessage = '同一ステータスの submit を検索'; | |
let $tbody = $('#main-container > div > div:nth-child(2) > div:nth-child(8) > table > tbody'); | |
$tbody.append('<tr><th></th><td class="text-center"></td></tr>'); | |
$tbody.append('<tr><th>†闇デバッガ†</th><td class="text-center"><button id="dark-debugger">' + btnMessage + '</button></td></tr>'); | |
let $btnDebugger = $('#dark-debugger'); | |
$btnDebugger.click(function(){ | |
if(numPage >= maxPage) | |
return; | |
// ボタンを使用不可にする | |
$(this).prop('disabled', true); | |
$(this).css('color', 'gray'); | |
numPage++; | |
// 同一タスク・同一言語・同一 verdict の提出一覧 URL を取得 | |
// 言語を限定するかどうかはオプションにしたほうがよさそう | |
let urlSameStatus = urlHome + '?f.Task=' + myStatus['Task'] + '&f.Language=' + myStatus['Language'] + '&f.Status=' + myStatus['Verdict'] + '&page=' + numPage; | |
// これは醜い ajax 3 重入れ子です | |
// 提出一覧 URL の html を取得 | |
$.ajax({ | |
type: 'GET', | |
url: urlSameStatus, | |
dataType: 'html', | |
success: function(html){ | |
let parser = new DOMParser(); | |
html = parser.parseFromString(html, "text/html"); | |
if(maxPage == undefined) | |
maxPage = $(html).find("#main-container > div > div:nth-child(3) > div.text-center > ul > li").last().text(); | |
$btnDebugger.text(btnMessage + ' (' + numPage + '/' + maxPage + ')'); | |
// 各提出の status を取得 | |
let statuses = getStatusesFromSubmissions(html); | |
for(let i = 0; i < /*1*/statuses.length; i++){ | |
let urlSubmission = statuses[i]['SubmissionURL']; | |
// 各提出の status を取得 | |
$.ajax({ | |
type: 'GET', | |
url: urlSubmission, | |
dataType: 'html', | |
success: function(data){ | |
let parser = new DOMParser(); | |
data = parser.parseFromString(data, "text/html"); | |
let WAStatus = getStatusFromSubmission(data); | |
if(myStatus['Verdicts'] == WAStatus['Verdicts'] && myStatus['Username'] != WAStatus['Username']){ | |
// テストケース単位で verdict が一致する他人の提出を発見 | |
// その人の提出一覧を見る | |
let urlUserSubmissions = WAStatus['UserSubmissionsURL']; | |
$.ajax({ | |
type: 'GET', | |
url: urlUserSubmissions, | |
dataType: 'html', | |
success: function(html){ | |
let parser = new DOMParser(); | |
html = parser.parseFromString(html, "text/html"); | |
// 各提出の status を取得 | |
let statuses = getStatusesFromSubmissions(html); | |
let idx = 0; | |
for(; idx < /*1*/statuses.length; idx++) | |
if(WAStatus['Time'] == statuses[idx]['Time']) | |
break; | |
for(let i = idx - 1; i >= 0; i--){ | |
if(WAStatus['Language'] == statuses[i]['Language'] && | |
WAStatus['Task'] == statuses[i]['Task'] && | |
statuses[i]['Verdict'] == 'AC'){ | |
if($result == undefined){ | |
$tbody.append('<tr><th>検索結果</th><td class="text-center" id="dd-search-result"></td></tr>'); | |
$result = $('#dd-search-result'); | |
} | |
let tmpHTML = $result.html(); | |
$result.html(tmpHTML + statuses[i]['Username'] + '\: <a href="' + urlSubmission + '">#' + urlSubmission.split('/').pop() + '</a> → <a href="' + statuses[i]['SubmissionURL'] + '">#' + statuses[i]['SubmissionURL'].split('/').pop() + '</a><br>'); | |
return; | |
} | |
} | |
}, | |
error: function(){ | |
console.log('failed to open URL: ' + urlUserSubmissions); | |
} | |
}); | |
} | |
}, | |
error: function(){ | |
console.log('failed to open URL: ' + urlSubmission); | |
} | |
}); | |
} | |
}, | |
error: function(){ | |
console.log('failed to open URL: ' + urlSameStatus); | |
} | |
}); | |
// クールタイム 30 秒 (応急処置) | |
setTimeout(function(){ | |
$btnDebugger.prop('disabled', false); | |
$btnDebugger.css('color', 'black'); | |
}, 30000); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment