Webwork Autofill

send a POST request based on the fetch text in the input box

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==UserScript==
// @name         Webwork Autofill
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  send a POST request based on the fetch text in the input box
// @author       You
// @match        webwork.math.ust.hk/*
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';
    var dodebug = false;
    var Advanced_Recursion = false;

    // Create the input box
    var inputBox = document.createElement('textarea');
    inputBox.id = 'fetchText';
    inputBox.rows = 1;
    inputBox.cols = 200;


    var inputBoxMin = document.createElement('textarea');
    inputBoxMin.id = 'boxMin';
    inputBoxMin.rows = 1;
    inputBoxMin.cols = 4;
    inputBoxMin.textContent = '-7';

    var inputBoxMax = document.createElement('textarea');
    inputBoxMax.id = 'boxMax';
    inputBoxMax.rows = 1;
    inputBoxMax.cols = 4;
    inputBoxMax.textContent = '7';

    var inputInterval = document.createElement('textarea');
    inputInterval.id = 'boxInterval';
    inputInterval.rows = 1;
    inputInterval.cols = 4;
    inputInterval.textContent = '50';

    var CorrectAns = document.createElement('div');
    CorrectAns.id = 'CorrectAns';
    CorrectAns.style.border = '1px solid black';
    CorrectAns.textContent = 'Correct Answers: ';
    CorrectAns.onclick =  function() {
        resultList = {};
        CorrectAns.textContent = 'Correct Answers: ';
        localStorage.setItem('resultList',JSON.stringify(resultList));
    };


    // Create the button
    var button = document.createElement('button');
    button.textContent = 'Send POST request';
    button.onclick = function () {
        // Get the fetch text from the input box
        var fetchText = document.getElementById('fetchText').value;
        executeFetchCommand(fetchText);
    };

    // Add the input box and the button after the specified element
    var element = document.getElementById('problem-nav');
    element.parentNode.insertBefore(inputBox, element.nextSibling);
    element.parentNode.insertBefore(button, inputBox.nextSibling);
    element.parentNode.insertBefore(inputBoxMin, button.nextSibling);
    element.parentNode.insertBefore(inputBoxMax, inputBoxMin.nextSibling);
    element.parentNode.insertBefore(inputInterval, inputBoxMax.nextSibling);
    element.parentNode.insertBefore(CorrectAns, inputInterval.nextSibling);


    var resultList = {};


    var localItems = ['fetchText', 'boxMin', 'boxMax', 'boxInterval'];

    function getLocalItem(localItems){
        localItems.forEach(function (item){
            if (localStorage.getItem(item) !== null) {
                document.getElementById(item).value = localStorage.getItem(item);
            }
        })
        if (localStorage.getItem('resultList') !== null){
            resultList = JSON.parse(localStorage.getItem('resultList'));
            CorrectAns.textContent = 'Correct Answers: '+JSON.stringify(resultList);
        }
    }
    getLocalItem(localItems);


    function debugoutput(text){
        if (dodebug){
            console.log(text);
        }
    }


    var executed = 0;
    var submitted = 0;

    function executeFetchCommand(fetchCommand) {
        // Remove semicolon from the end of the command
        fetchCommand = fetchCommand.trim();
        if (fetchCommand.endsWith(';')) {
            fetchCommand = fetchCommand.slice(0, -1);
        }

        // Get all answer input boxes
        var answerInputs = document.querySelectorAll('input.codeshard');
        var answerIds = [];
        answerInputs.forEach(function(input) {
            if (input.previousSibling && input.previousSibling.checked) {
                answerIds.push(input.id);
            }
        });
        answerIds = answerIds;

        var min = Number(document.getElementById('boxMin').value);
        var max = Number(document.getElementById('boxMax').value);
        var interval = Number(document.getElementById('boxInterval').value);


        var current_empty_answers = (answerIds.length-Object.keys(resultList).length);
        var current_filled_answers = Object.keys(resultList).length;
        var max_cases = Advanced_Recursion ? (max-min+1)**current_empty_answers : (max-min+1)*current_empty_answers;
        console.log("current empty answers: "+current_empty_answers);

        var progress = document.createElement('progress');
        progress.id = 'ProgressBar'
        progress.value = 0;
        progress.max = (max-min+1)**current_empty_answers;


        var progtxt = document.createElement('div');
        progtxt.id = 'progresstext';
        progtxt.style.border = '1px solid black';
        progtxt.textContent = `${submitted} (submitted) / ${executed} (executed) / ${max_cases} (max)`;
        element.parentNode.insertBefore(progress, CorrectAns.nextSibling);
        element.parentNode.insertBefore(progtxt, progress.nextSibling);

        function replaceAnswerInFetchCommand(fetchCommand, answerId, answerValue) {
            //console.log("received AnsID/Ans: "+ answerId+"/"+answerValue)
            // Create a regular expression to find the answer in the fetch command
            var regex = new RegExp('(?<=name=\\\\"'+answerId+'\\\\"\\\\r\\\\n\\\\r\\\\n).*?(?=\\\\r\\\\n)', 'g');
            // Replace the answer in the fetch command
            var newFetchCommand = fetchCommand.replace(regex, answerValue);
            return newFetchCommand;
        };
        // Try all combinations of answers

        console.log('answerIds:');
        console.log(answerIds);

        function recurseFor(recurseCommand, ansIndex){
            debugoutput('ansIndex = ' + ansIndex);
            if (ansIndex>=0) {
                if (('Box'+(ansIndex+1)) in resultList){
                    var value = resultList['Box'+(ansIndex+1)];
                    debugoutput('skipping with '+ value + ' in Box'+ (ansIndex+1));
                    document.getElementById(answerIds[ansIndex]).value = value;
                    recurseCommand = replaceAnswerInFetchCommand(recurseCommand, answerIds[ansIndex], value);
                    recurseFor(recurseCommand, ansIndex-1);
                }else{
                    for (var i = min; i<=max; i++) {
                        debugoutput('For Delay: ansIndex = ' + ansIndex+' Power='+Math.max(ansIndex-current_filled_answers,0));
                        var timeout = (max-min+1)**(Math.max(ansIndex-current_filled_answers,0))*(i-min)*interval;
                        debugoutput('setting delay for Depth '+ (ansIndex-1) + ' with '+ timeout+'ms');
                        setTimeout(function (recurseCommand,ansIndex,i){
                            document.getElementById(answerIds[ansIndex]).value = i;
                            recurseCommand = replaceAnswerInFetchCommand(recurseCommand, answerIds[ansIndex], i);
                            recurseFor(recurseCommand, ansIndex-1);
                        },timeout, recurseCommand, ansIndex, i);
                        // to set time interval for each fetch
                        // numBranch**(maxDepth-Depth)*Branch_i*TARGET_INTERVAL
                    }
                }
            }else{
                submitted+=1;
                progtxt.textContent = `${submitted} (submitted) / ${executed} (executed) / ${max_cases} (max)`;
                if (!dodebug){
                    eval(recurseCommand);
                }
            }
        };

        var answer_list = [];
        answerIds.forEach(function(item,index){
            answer_list[index] = max;
        });
        debugoutput('initial answer_list: '+JSON.stringify(answer_list));


        //TODOOO: ALL TOGETHER, NO SEPERATION
        function recurse(ansIndex, ansValue){
            debugoutput(`ansIndex = ${ansIndex}, ansValue = ${ansValue}`);
            debugoutput(answer_list);
            if (ansIndex<0){
                return;
            }
            else if(ansValue<min){
                recurse(ansIndex-1, max);
            }
            else{
                if (('Box'+(ansIndex+1)) in resultList){
                    ansValue = resultList['Box'+(ansIndex+1)];
                    answer_list[ansIndex] = ansValue;

                    document.getElementById(answerIds[ansIndex]).value = ansValue;

                    recurse(ansIndex-1, max);
                }else{
                    answer_list[ansIndex] = ansValue;

                    document.getElementById(answerIds[ansIndex]).value = ansValue; //Fill to the textbox

                    answer_list.forEach(function (item,index){
                        recurseCommand = replaceAnswerInFetchCommand(recurseCommand, answerIds[index], item);
                    });
                    if (!dodebug){
                        submitted+=1;
                        progtxt.textContent = `${submitted} (submitted) / ${executed} (executed) / ${max_cases} (max)`;
                        eval(recurseCommand);
                    }
                    debugoutput(`sleep ${interval}ms for next call`);
                    setTimeout(recurse, interval, ansIndex, ansValue-1);
                }
            }
        };

        function AdvancedRecurse(ansIndex, ansValue){
            debugoutput(`ansIndex = ${ansIndex}, ansValue = ${ansValue}`);
            debugoutput(`start:`+answer_list.join());
            if (ansIndex<0){
                return;
            }
            else if(ansValue<min){
                return;
            }
            else{
                /*if (('Box'+(ansIndex+1)) in resultList){
                    ansValue = resultList['Box'+(ansIndex+1)];
                    answer_list[ansIndex] = ansValue;

                    document.getElementById(answerIds[ansIndex]).value = ansValue;

                    AdvancedRecurse(ansIndex-1, max);
                }else{*/
                    AdvancedRecurse(ansIndex -1, ansValue);
                    answer_list[ansIndex] = ansValue;
                    debugoutput(`Added: `+answer_list);
                    document.getElementById(answerIds[ansIndex]).value = ansValue; //Fill to the textbox

                    answer_list.forEach(function (item,index){
                        recurseCommand = replaceAnswerInFetchCommand(recurseCommand, answerIds[index], item);
                    });

                    if (!dodebug){
                        submitted+=1;
                        progtxt.textContent = `${submitted} (submitted) / ${executed} (executed) / ${max_cases} (max)`;
                        eval(recurseCommand);
                    }

                    debugoutput(`sleep ${interval}ms for next call`);
                    setTimeout(AdvancedRecurse, interval, ansIndex , ansValue -1);


                //}
            }
        };

        fetchCommand = `function fetchWithRetry(retryCount = 5) {
            return ` + fetchCommand + `.then(response => response.text())
            .then(data => {
            let parser = new DOMParser();
            let htmlDoc = parser.parseFromString(data, 'text/html');
            let rows = htmlDoc.querySelectorAll('.attemptResults tr');
            let enteredArray = [];
            let resultArray = [];
            for(let i = 1; i < rows.length; i++) { // skip the header row
                let entered = rows[i].children[0].textContent;
                let result = rows[i].children[2].textContent;
                if (result === 'correct') {
                    resultList['Box'+i] = entered;
                    CorrectAns.textContent = 'Correct Answers (click to reset) : '+JSON.stringify(resultList);
                    localStorage.setItem('resultList',JSON.stringify(resultList));
                }
                enteredArray.push(entered);
                resultArray.push(result);
            }
            console.log('Entered: ' + enteredArray.join(', '));
            console.log('Result: ' + resultArray.join(', '));
            executed+=1;
            progtxt.textContent = \`\${submitted} (submitted) / \${executed} (executed) / \${max_cases} (max)\`;
            progress.value = executed;
            })
            .catch((error) => {
            var delay = 1000;
            delay+= Math.random()*2000;
            console.error('Unable to fetch! Retry remaining: ' + retryCount + ' in '+ delay+'ms ');
            if (retryCount >= 1) {
                return setTimeout(delay,fetchWithRetry,retryCount - 1);
            }
            });
            }
            fetchWithRetry();`;
        var recurseCommand = fetchCommand;

        if (Advanced_Recursion) {
            AdvancedRecurse(answerIds.length-1, max);
        }else {
            recurse(answerIds.length-1, max);
        }



    }

    // Add checkboxes to all answer input boxes
    var answerInputs = document.querySelectorAll('input.codeshard');
    answerInputs.forEach(function(input,index) {
        var checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        input.parentNode.insertBefore(checkbox, input);
        checkbox.checked = true;
    });



    function setLocalItem(localItems){
        localItems.forEach(function (item){
            document.getElementById(item).addEventListener('change', function() {
            localStorage.setItem(item, this.value);
            });
        });
        document.getElementById('CorrectAns').addEventListener('change', function() {
            localStorage.setItem('resultList',JSON.stringify(resultList));
        });
    }
    setLocalItem(localItems);

})();