// ==UserScript==
// @name Show only pretest result when participating in virtual contest in Codeforces
// @version 0.0
// @description Show only pretest result when participating in virtual contest in Codeforces
// @match *://codeforces.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function(){
//OK:1,
//COMPILATION_ERROR:1,
//CRASHED:1, // example: https://codeforces.com/contest/566/submission/42421894
//FAILED:1, // example: https://codeforces.com/contest/566/submission/16877130
/*
const wrong_verdicts={
WRONG_ANSWER:1,
TIME_LIMIT_EXCEEDED:1,
RUNTIME_ERROR:1,
MEMORY_LIMIT_EXCEEDED:1,
IDLENESS_LIMIT_EXCEEDED:1,
0:0}*/
const pretest_passed_verdicts={
SKIPPED:1,
CHALLENGED:1, // aka hacked
0:0}
function getPassedTestCount(x){
return x.passedTestCount
}
//let csrf_token=Codeforces.getCsrfToken()
//let csrf_token=document.getElementsByName('X-Csrf-Token')[0].content
let csrf_token=undefined // TODO
async function getNewCsrfToken(){
let data=await $.get('/')
return $.parseHTML(data).filter(z=>z.name=='X-Csrf-Token').map(z=>z.content)
}
/*
$.ajax({
url: '//codeforces.com/data/submitSource',
type: 'post',
data: {
submissionId: 60874505
},
headers: {
'X-Csrf-Token': ''
},
dataType: 'json',
success: function (data) {
console.info(data);
}
});
*/
async function isValid(submissionId){
// Return whether a skipped submission has no
// wrong answer/run time error/memory limit exceeded/etc. test case, excluding hacks (can
// happen when the user cheated in the contest). Example
// https://codeforces.com/contest/1221/submission/60879848.
/*
let data=$.post('//codeforces.com/data/submitSource',{
submissionId: submissionId,
csrf_token:Codeforces.getCsrfToken()
})
*/
let data
try{
data=await $.ajax({
url: '//codeforces.com/data/submitSource',
type: 'post',
data: {submissionId: submissionId},
headers: {'X-Csrf-Token': csrf_token},
dataType: 'json'
})
}catch(e){
csrf_token=await getNewCsrfToken()
data=await $.ajax({
url: '//codeforces.com/data/submitSource',
type: 'post',
data: {submissionId: submissionId},
headers: {'X-Csrf-Token': csrf_token},
dataType: 'json'
})
}
return Object.keys(data).filter(
x=>x.startsWith('verdict#')&&data[x]!='OK'
).length==0
}
async function getPretestCount(contestId){
if(searchParams.has('mock_pretest_count'))
return new Proxy({}, { get: _=>[10, 20] })
const key='pretest_count_'+contestId
{
const stored_result=GM_getValue(key)
if(stored_result!==undefined)
return JSON.parse(stored_result)
}
const data=await $.get('//codeforces.com/api/contest.status?contestId='+contestId+'&from=1&count=100000000')
let result={} // {problemIndex /* A/B/C/... */: [minPretestCount, maxPretestCount]}
for(const problemIndex of new Set(data.result.map(x=>x.problem.index))){
let problemResult=data.result.filter(x=>
x.problem.index==problemIndex&&
x.author.participantType=="CONTESTANT"
)
let minPretestCount,maxPretestCount
s1=problemResult.filter(x=>
pretest_passed_verdicts[x.verdict]&&x.passedTestCount!=0
// it's possible for SKIPPED submissions to have 0 tests passed when
// the user submits the second solution before the first one is judged
)
s1.sort((a,b)=>b.passedTestCount-a.passedTestCount)
if(s1.length!=0&&(await isValid(s1[0].id))){
minPretestCount=maxPretestCount=s1[0].passedTestCount
}else{
minPretestCount=1+Math.max(...
problemResult.filter(
// x=>x.testset=="PRETESTS"&&wrong_verdicts[x.verdict]
// cannot be "skipped" -> must fail on pretest
x=>x.testset=="PRETESTS"
).map(getPassedTestCount)
)
maxPretestCount=Math.min(...
problemResult.filter(
x=>x.testset=="TESTS"
).map(getPassedTestCount)
)
}
result[problemIndex]=[minPretestCount,maxPretestCount]
}
GM_setValue(key,JSON.stringify(result))
console.log(result)
return result
}
/*
function getContestId(){
return location.pathname.match('^/contest/(\\d+)')[1]
}
const contestId=getContestId()
*/
let searchParams=new URL(location).searchParams
// always_show, reset_button
let cache={} // problemId -> result
let cacheSubmissions={} // submissionId -> item
let participantId // assume participantId is fixed
/*
function get_csrf_token(){ // use Codeforces.getCsrfToken()
return csrf_token
}
*/
if(searchParams.has('always_show')||location.href.match('://codeforces.com/contest/\\d*/my')){
console.log('start')
function restoreAll(){
observer.disconnect()
document.querySelectorAll('span').forEach(function(t){
if(t.__oldTextContent!==undefined){
t.textContent=t.__oldTextContent
t.className=t.__oldClassName
}
})
}
let button
function clickResetButton(){
restoreAll()
if(searchParams.has('reset_button')){
document.body.removeChild(button)
button=undefined
}
}
function createResetButton(){
if(button===undefined){
if(searchParams.has('reset_button')){
button=document.createElement('button')
button.innerHTML='Reset'
button.onclick=clickResetButton
document.body.appendChild(button)
}
}
}
let pendingNodes=[]
let getPretestCountRunning=false
let pretestCount={}
function processPendingNodes(){
let oldPendingNodes=pendingNodes
pendingNodes=[]
oldPendingNodes.forEach(processSpan)
}
const loadingText='Loading...'
function processSpan(t){
if(t.textContent==='Running') // before any test
return
console.log('processSpan',t.textContent)
let modified=t.textContent===loadingText||t.textContent==='Pretest passed'||t.textContent.includes(' pretest ')
if(!modified){
t.__oldTextContent=t.textContent
t.__oldClassName=t.className
}
let contestId,problemIndex
{
let tableRow=t
while(tableRow.tagName!='TR')
tableRow=tableRow.parentNode
const problemUrl=tableRow.children[3].children[0].href
const match=problemUrl.match('/contest/(\\d*)/problem/(.*)$$$')||problemUrl.match('/problemset/problem/(\\d*)/(.*)$$$')
// the second format is only used in problemset status page (when always_show is on)
contestId=match[1]
problemIndex=match[2]
}
if(pretestCount[contestId]===undefined||pretestCount[contestId]==='running'){
t.textContent=loadingText
t.className=''
pendingNodes.push(t)
if(pretestCount[contestId]!=='running'){
getPretestCount(contestId).then(function(result){
pretestCount[contestId]=result
processPendingNodes() // for pages different from contest/my this may cause the span to be push back to pendingNodes list
})
pretestCount[contestId]='running'
}
return
}
if(t.__oldTextContent==='Accepted'){
createResetButton()
t.textContent='Pretest passed'
t.className=t.__oldClassName
return
}
t.classList.replace('verdict-accepted','verdict-rejected')
if(!(
t.__oldClassName.match(/\bverdict-rejected\b/)||
t.__oldClassName.match(/\bverdict-waiting\b/)
)) throw new Error
try{
let wrongTestIndex=t.__oldTextContent.match(/ on test (\d+)$/)[1]
if(wrongTestIndex<=pretestCount[contestId][problemIndex][0]){
t.textContent=t.__oldTextContent.replace('on test','on pretest')
t.className=t.__oldClassName // rejected || waiting
}else if(wrongTestIndex<=pretestCount[contestId][problemIndex][1]){
t.textContent='???'
t.className=''
}else{
t.textContent='Pretest passed'
t.className='verdict-accepted'
}
}catch(e){
console.log(t.__oldTextContent,e)
}
//let problemId=tableRow.children[3].getAttribute('data-problemId') // int-parseable string
//let submissionId=parseInt(tableRow.children[0].textContent)
//if(participantId===undefined)
// participantId=parseInt(tableRow.children[2].getAttribute('data-participantId'))
}
let observer=new MutationObserver(function(mutations, observer){
for (let r of mutations){
for (let t of r.addedNodes){ // t must be in local scope
if(t.tagName==='SPAN'){
if(t.classList.contains('contest-state-phase')){
// do not execute userscript when not in virtual contest
if(
t.textContent==='Finished' // TODO 'Virtual participation'?
&&!searchParams.has('always_show')
){
clickResetButton()
observer.disconnect()
return
}
}else if(t.classList.contains('verdict-accepted')||t.classList.contains('verdict-rejected')||t.classList.contains('verdict-waiting')){
processSpan(t)
}
}else if(t.tagName==='DIV'){
if(t.classList.contains('jGrowl-notification')){
let z=t.getElementsByClassName('message')
if(z.length!==0&&z[0].textContent!='???')
z[0].textContent='???'
}
}
}
}
})
observer.observe(document,{
childList:true,
subtree:true,
attributes:true
});
window.addEventListener('load',function(){
})
}
})()
// TODO incomplete (rewrite standings table)
//if(location.href.match('://codeforces.com/contest/\\d*/standings')){
// let observer=new MutationObserver(function(mutations, observer){
// /*
// let logout_link=document.querySelector('[href$="logout"]')
// if(logout_link!==null)
// let handle=logout_link.previousElementSibling.textContent)
// */
//
// GM_addStyle(String.raw`
// .highlighted-row td[problemid] {
// visibility: hidden;
// }
// `)
// observer.disconnect()
// })
// observer.observe(document,{
// childList:true,
// subtree:true,
// attributes:true
// });
//}
// vim: set ts=4 sw=4 fdm=indent: