See CodeChum hidden test cases

See hidden test cases as if they are not hidden in codechum after you press submit

Versión del día 07/10/2022. Echa un vistazo a la versión más reciente.

// ==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();

})();