Webwork Autofill

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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

})();