Khan Academy Problem Solver
As of
// ==UserScript==
// @name KhanHack
// @namespace https://greatest.deepsurf.us/users/783447
// @version 5.0
// @description Khan Academy Problem Solver
// @author Logzilla6 - IlyTobias
// @match https://*.khanacademy.org/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=khanacademy.org
// ==/UserScript==
let mainMenu = document.createElement('div');
mainMenu.id = 'mainMenu';
mainMenu.style.position = 'fixed';
mainMenu.style.bottom = '.5vw';
mainMenu.style.left = '19vw';
mainMenu.style.width = '300px';
mainMenu.style.height = '400px';
mainMenu.style.backgroundColor = '#123576';
mainMenu.style.border = '3px solid #07152e';
mainMenu.style.borderRadius = '20px';
mainMenu.style.padding = '10px';
mainMenu.style.color = "white";
mainMenu.style.fontFamily = "Noto sans";
mainMenu.style.fontWeight = "500";
mainMenu.style.transition = "all 0.3s ease";
let answerBlocks = [];
let currentCombinedAnswer = '';
const setMainMenuContent = () => {
mainMenu.innerHTML =`
<div id="menuContent" style="display: flex; flex-direction: column; align-items: center; position: relative; gap: 10px; opacity: 1; transition: opacity 0.5s ease;">
<head>
<img id="discordIcon" src="https://i.ibb.co/grF973h/discord.png" alt="Discord" style="position: absolute; left: 7px; top: 8px; width: 24px; height: 24px; opacity: 1; transition: opacity 0.5s ease; cursor: pointer;" />
<img id="headerImage" src="https://i.ibb.co/pX592fL/khanhack.png" style="width: 170px; opacity: 1; transition: opacity 0.5s ease;" />
<img id="gearIcon" src="https://i.ibb.co/q0QVKGG/gearicon.png" alt="Settings" style="position: absolute; right: 7px; top: 7px; width: 24px; height: 24px; opacity: 1; transition: opacity 0.5s ease; cursor: pointer;" />
</head>
<div id="answerList" style="width: 100%; display: flex; flex-direction: column; gap: 10px;"></div>
</div>
<img id="toggleButton" src="https://i.ibb.co/RpqPcR1/hamburger.png" style="width: 20px; height: 20px; left: 0; right: 0;" class="toggleButton">
<img id="clearButton" src="https://i.ibb.co/VYs1BPQ/can.png" style="width: 20px; height: 20px; bottom: 10px; right: 8px; position: absolute; cursor: pointer;" class="clearButton">
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
.toggleButton {
position: absolute;
bottom: 3px;
left: 2px;
background-color: #123576;
color: white;
border: none;
padding: 5px 7px;
border-radius: 15px;
cursor: pointer;
font-size: 15px;
font-family: "Noto Sans";
font-weight: 500;
}
.block {
width: 93.5%;
height: 40px;
background-color: #f0f0f0;
padding: 10px;
border-radius: 10px;
opacity: 1;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.5s ease;
}
.answer {
margin: 0;
text-align: center;
color: black;
font-family: "Noto Sans";
font-weight: 500;
}
.imgBlock img {
width: 250px;
border-radius: 10px;
}
</style>
`
addToggle();
addSettings();
addDiscord();
addClear();
const answerList = document.getElementById('answerList');
answerList.innerHTML = '';
answerBlocks.forEach(block => {
if (block.type === 'text') {
addAnswerBlock(block.content, false);
} else if (block.type === 'image') {
addImgAnswerBlock(block.content, false);
}
});
};
const addAnswerBlock = (answer, save = true) => {
const answerList = document.getElementById('answerList');
const block = document.createElement('div');
block.className = 'block';
const p = document.createElement('p');
p.className = 'answer';
p.innerText = `Answer: ${answer}`;
block.appendChild(p);
answerList.appendChild(block);
if (save) {
answerBlocks.push({ type: 'text', content: answer });
}
};
const addImgAnswerBlock = (imgSrc, save = true) => {
const answerList = document.getElementById('answerList');
const block = document.createElement('div');
block.className = 'block imgBlock';
const img = document.createElement('img');
img.src = imgSrc;
block.appendChild(img);
answerList.appendChild(block);
if (save) {
answerBlocks.push({ type: 'image', content: imgSrc });
}
};
let isMenuVisible = true;
const addToggle = () => {
document.getElementById('toggleButton').addEventListener('click', function() {
const clearButton = document.getElementById('clearButton');
if (isMenuVisible) {
mainMenu.style.height = '15px';
mainMenu.style.width = '15px';
document.getElementById('menuContent').style.opacity = '0';
clearButton.style.opacity = '0';
setTimeout(() => {
document.getElementById('menuContent').style.display = 'none';
clearButton.style.display = 'none';
}, 50);
} else {
mainMenu.style.height = '400px';
mainMenu.style.width = '300px';
document.getElementById('menuContent').style.display = 'flex';
clearButton.style.display = 'block';
setTimeout(() => {
document.getElementById('menuContent').style.opacity = '1';
clearButton.style.opacity = '1';
}, 100);
}
isMenuVisible = !isMenuVisible;
});
};
const addSettings = () => {
document.getElementById('gearIcon').addEventListener('click', function() {
mainMenu.innerHTML = `
<div id="settingsContent" style="display: flex; flex-direction: column; align-items: center; position: relative; opacity: 1; transition: opacity 0.5s ease;">
<img id="backArrow" src="https://i.ibb.co/Jt4qrD7/pngwing-com-1.png" alt="Back" style="position: absolute; left: 7px; top: 3px; width: 24px; height: 24px; opacity: 1; transition: opacity 0.5s ease; cursor: pointer;" />
<h3 style="margin: 0; text-align: center; color: white; font-family: Noto sans; font-weight: 500;">Settings Menu</h3>
<p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Ghost Mode: (Coming Soon)</p>
<p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Auto Answer: (Coming Soon)</p>
<p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Point Farmer: (Coming Soon)</p>
<p style="text-align: center; color: white; font-family: Noto sans; margin-top: 180px;">KhanHack™</p>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
</style>
</div>
`
document.getElementById('backArrow').addEventListener('click', setMainMenuContent);
});
};
const addDiscord = () => {
document.getElementById('discordIcon').addEventListener('click', function() {
window.open('https://discord.gg/khanhack', '_blank');
});
};
const addClear = () => {
document.getElementById('clearButton').addEventListener('click', function() {
location.reload()
});
};
document.body.appendChild(mainMenu);
setMainMenuContent();
let originalJson = JSON.parse;
JSON.parse = function (jsonString) {
let parsedData = originalJson(jsonString);
try {
let itemData = JSON.parse(parsedData.data.assessmentItem.item.itemData);
for (let widgetKey in itemData.question.widgets) {
let widget = itemData.question.widgets[widgetKey];
switch (widget.type) {
case "numeric-input":
handleNumeric(widget);
break;
case "radio":
handleRadio(widget);
break;
case "expression":
handleExpression(widget);
break;
case "dropdown":
handleDropdown(widget);
break;
default:
console.log("Unknown widget: " + widget.type);
break;
}
}
if (currentCombinedAnswer.trim() !== '') {
addAnswerBlock(currentCombinedAnswer.trim());
currentCombinedAnswer = '';
}
} catch (error) {
}
return parsedData;
};
function cleanLatexExpression(answer) {
return answer
.replace(/\\times/g, '×')
.replace(/\\frac{([^}]*)}{([^}]*)}/g, '($1/$2)')
.replace(/\\cdot/g, '⋅')
.replace(/\\left|\\right/g, '')
.replace(/[\$]/g, '')
.replace(/\^/g, '^')
.replace(/\\(?:[a-zA-Z]+)/g, '')
.replace(/(?<!\\){|}/g, '');
}
function handleRadio(widget) {
let content = widget.options.choices.filter(item => item.correct === true).map(item => item.content);
let combinedAnswers = '';
let hasImage = false;
let imageUrl = null;
content.forEach(answer => {
const regex = answer.match(/:\/\/(.*?)\)/);
if (regex) {
const finalImg = "https" + regex[0].slice(0, -1) + ".svg";
hasImage = true;
imageUrl = finalImg;
} else {
const cleanedAnswer = cleanLatexExpression(answer);
combinedAnswers += cleanedAnswer + ', ';
}
});
combinedAnswers = combinedAnswers.trim().replace(/,$/, '');
if (combinedAnswers !== '') {
currentCombinedAnswer += `${combinedAnswers}` ;
}
if (hasImage && imageUrl) {
addImgAnswerBlock(imageUrl);
}
}
function handleNumeric(widget) {
const numericAnswer = widget.options.answers[0].value;
currentCombinedAnswer += `${numericAnswer}, `;
}
function handleExpression(widget) {
let expressionAnswer = widget.options.answerForms[0].value;
currentCombinedAnswer += `${expressionAnswer}` ;
}
function handleDropdown(widget) {
let content = widget.options.choices.filter(item => item.correct === true).map(item => item.content);
currentCombinedAnswer += `${content[0]},` ;
}