Skip to content

Instantly share code, notes, and snippets.

@cielavenir
Last active June 14, 2020 09:14
Show Gist options
  • Save cielavenir/c012d330a25f358eff2e911c1b5d5a5b to your computer and use it in GitHub Desktop.
Save cielavenir/c012d330a25f358eff2e911c1b5d5a5b to your computer and use it in GitHub Desktop.
ac-DarkDebugger
// ==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