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