KhanHack

Khan Academy Problem Solver

Versão de: 17/09/2024. Veja: a última versão.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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]},` ;
}