// ==UserScript==
// @name See CodeChum hidden test cases
// @namespace http://tampermonkey.net/
// @version 0.3
// @description See hidden test cases as if they are not hidden in codechum after you press submit
// @author Lebron Samson
// @match https://citu.codechum.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=codechum.com
// @grant none
// @license none
// ==/UserScript==
// url regex
const testCaseEndpoint = new RegExp('answers-v4\/[0-9]+\/executev2', 'g');
const changeProblemEndPoints = [
"v3/page-visits/",
"v3/answers-v4/",
"v3/answer-comments/count/"
];
// Stylings
const style_testCaseContent = `display: grid; grid-row-gap: 16px; grid-template-column: minmax(0, 1fr); margin: 16px 0 8px;`;
const style_testCaseContent_title = `margin: 8px 0 12px;`;
const style_Text_n = `color: #b0b9bf;`;
const style_Text_heading = `font-family: Monsterrat,sans-serif; font-size: 1rem; line-height: 1.25; font-weight: 700; font-style: normal;`
const style_pre = `color: rgb(204, 204, 204); border-radius: 8px; background: rgb(45, 45, 45); font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none; padding: 1em; margin: 0.5em 0px; overflow: auto;`;
const style_Code_sm = `background-color: #2d3845 !important;`
const style_code_el = `color: rgb(204, 204, 204); font-size: 14px; background: none; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none;`;
// Test Case template
const testCaseDiv = document.createElement("div");
testCaseDiv.innerHTML = `
<div style="${style_testCaseContent}">
<div>
<h6 style="${style_testCaseContent_title}${style_Text_n}${style_Text_heading}">
Expected Output
</h6>
<div>
<pre style='${style_pre}${style_Code_sm}'><code style='${style_code_el}'>
put test cases in here
</code></pre>
</div>
</div>
</div>
`;
function getDOM(targetCL, tag){
// fuck off obfuscation
return Array.from(document.querySelectorAll("div")).filter(e => e.classList.length > 0).filter(e => Array.from(e.classList).some(cl => cl.includes(targetCL)));
}
function getProblemName(){
return getDOM("styles_QuestionInfo_head", "div")[0].querySelector("h4").innerText;
}
function event_displayHiddenCaseOnClick(e){
// if next sibling (test case container) is hidden, then show, else hide
this.nextElementSibling.style.display = (this.nextElementSibling.style.display != "block") ? "block" : "none";
}
function tryToLoadCases(arg){
// I don't have any idea why would codechum get 2 request of this, so I'm making a counter
if(!this.count) this.count = 0;
this.count++;
if(this.count == 1) return; // for the first request
else this.count = 0; // for the second request
const currProb = getProblemName();
// try to find the test case saved in the problem
let saved = sessionStorage.getItem("saved_test_cases");
if(!saved) return;
saved = JSON.parse(saved);
const matches = saved.filter(e => e.problem == currProb);
console.log(matches);
if(matches.length){
displayCase(matches[0].cases);
}
}
function displayCase(cases){
let testCaseCont = getDOM("styles_InputOutput_testCases", "div");
if(testCaseCont.length == 0) return;
// if there is a constraint, then the test case content is the second child
testCaseCont = ((testCaseCont.length > 1) ? testCaseCont[1] : testCaseCont[0]).children[1];
testCaseCont.children.forEach( (div, i) => {
if(!div.children[0].disabled) return; // only modify hidden test cases
const caseCont = testCaseDiv.cloneNode(true);
caseCont.querySelector('code').innerHTML = cases[i].trim();
// if div is not yet marked, add an event listener to the button
if(div.dataset.marked != "true"){
div.children[0].disabled = false;
div.children[0].addEventListener("click", event_displayHiddenCaseOnClick);
div.children[0].style.cursor = "pointer";
}
// set the div as marked
div.dataset.marked = "true";
div.append(caseCont);
div.children[1].style.display = "none";
});
}
function saveCase(arg){
const prob = getProblemName();
let saved = sessionStorage.getItem("saved_test_cases") || [
{
problem: prob,
cases: arg.cases
}
];
if(typeof saved == "string"){
saved = JSON.parse(saved);
// check if problem is already saved, if not then save
if(!saved.some(e => e.problem == prob)) {
saved.push({
problem: prob,
cases: arg.cases
});
}
}
sessionStorage.setItem("saved_test_cases", JSON.stringify(saved));
}
function receiveCases(arg){
let res;
// TODO: Check if non-interactive
try {
res = JSON.parse(arg.response);
}catch (err){
return;
}
const testCase = {
id: res.id,
answer_id: res.answer_id,
cases: res.test_case_statuses.map(e => e.test_case.output)
}
displayCase(testCase.cases);
saveCase(testCase);
}
(function() {
'use strict';
// Modify open and send requests
var open = window.XMLHttpRequest.prototype.open,
send = window.XMLHttpRequest.prototype.send;
function openReplacement(method, url, async, user, password) {
this._url = url;
return open.apply(this, arguments);
}
function sendReplacement(data) {
if(this.onreadystatechange) {
this._onreadystatechange = this.onreadystatechange;
}
// if you want to modify send requests
this.onreadystatechange = onReadyStateChangeReplacement;
return send.apply(this, arguments);
}
var flag_changeProb = [0, 0, 0];
function onReadyStateChangeReplacement() {
// modify here received requests
// for submitting
if(testCaseEndpoint.test(this._url)) receiveCases(this);
// for changing problems (contains chain request)
else if(changeProblemEndPoints.some(e => this._url.includes(e))){
flag_changeProb[changeProblemEndPoints.findIndex(e => this._url.includes(e))]++;
if(flag_changeProb.every(e => e == 3)) tryToLoadCases(this);
}else{
// reset all flags
flag_changeProb = [0, 0, 0];
}
if(this._onreadystatechange) {
return this._onreadystatechange.apply(this, arguments);
}
}
window.XMLHttpRequest.prototype.open = openReplacement;
window.XMLHttpRequest.prototype.send = sendReplacement;
var request = new XMLHttpRequest();
request.open('GET', '.', true);
request.send();
})();