KhanHack

Khan Academy Problem Solver

Fra 17.09.2024. Se den seneste versjonen.

// ==UserScript==
// @name         KhanHack
// @namespace    https://greasyfork.org/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]},` ;
}