KhanHack

Sloppy lazybones anti-homework production

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         KhanHack
// @namespace    http://khan.dyntech.cc
// @version      6.0
// @description  Sloppy lazybones anti-homework production
// @author       DynTech
// @match        *://*.khanacademy.org/*
// @oicon        https://dyntech.cc/favicon?q=khan.dyntech.cc
// @icon         https://cdn.dyntech.cc/r/khanhack.png
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    // This FREE script was extracted from a $7.99/month extension. You're welcome.
    (function() {
    const originalFetch = window.fetch;

    // Check for an existing container and restore it if minimized
    let mainContainer = document.getElementById('questionDataContainer');
    if (mainContainer) {
        // If the container is minimized, restore it
        if (mainContainer.classList.contains('minimized')) {
            mainContainer.classList.remove('minimized');
            const restoreButton = document.getElementById('restoreButton');
            restoreButton.classList.remove('minimized');
            const minimizeButton = document.getElementById('minimizeButton');
            minimizeButton.style.display = 'block';
        }
        return; // Prevent adding a new container
    }

    // Create a new main container to hold the output data
    mainContainer = document.createElement('div');
    mainContainer.id = 'questionDataContainer';

    // Ensure the body is loaded before appending
    if (document.body) {
        document.body.appendChild(mainContainer);
    } else {
        window.addEventListener('DOMContentLoaded', () => {
            document.body.appendChild(mainContainer);
        });
    }

    // Style for the main container and question divs
    const style = document.createElement('style');
    style.innerHTML = `
        @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap');

        #questionDataContainer {
            position: fixed;
            top: 10px;
            right: 10px;
            width: 300px;
            height: 500px;
            overflow-y: auto;
            background-color: #1a1a1a;
            border: 2px solid #131313;
            padding: 10px;
            z-index: 9999;
            font-family: Poppins, sans-serif;
            font-size: 12px;
            border-radius: .8vw;
            color: white;
            transition: all .3s ease;
        }

        #questionDataContainer::-webkit-scrollbar {
            width: .6vw;
            height: 12px;
        }

        #questionDataContainer::-webkit-scrollbar-track {
            background: #141414;
        }

        #questionDataContainer::-webkit-scrollbar-thumb {
            background-color: #24c39d;
            border-radius: 1vw;
        }

        #questionDataContainer.minimized {
            width: 67px;
            height: 38px;
            overflow: hidden;
            border-radius: 10px;
            padding: 0;
        }

        #minimizeButton {
            position: absolute;
            top: 5px;
            right: 5px;
            background-color: #333;
            color: white;
            border: none;
            border-radius: 5px;
            font-size: 12px;
            padding: 5px;
            cursor: pointer;
            font-family: Poppins, sans-serif;
            transition: all .3s ease;
        }

        #minimizeButton:hover {
            background-color: #444;
            transition: all .3s ease;
        }

        #questionDataContainer.minimized #khanHackHeader {
            opacity: 0;
        }

        #khanHackHeader {
            text-align: center;
            font-family: Poppins, sans-serif;
            margin-bottom: 0px;
            font-size: 28px;
            color: #fff;
        }

        #khanHackHeader span {
            color: #24c39d;
        }

        .question-div {
            margin-bottom: 10px;
            padding: 10px;
            border-radius: 5px;
            color: black;
            overflow: hidden;
        }

        .radio-div {
            background-color: #e0ffe0;
        }

        .expression-div {
            background-color: #fff0f0;
        }

        .dropdown-div {
            background-color: #f0f0ff;
        }

        .orderer-div {
            background-color: #f5f5dc;
        }

        .input-div {
            background-color: #ffefd5;
        }

        .plotter-div {
            background-color: #ffe4b5;
        }

        .no-support-div {
            background-color: #ffcccc;
        }

        #answerToggle, #refreshButton {
            display: block;
            margin: 10px auto;
            padding: 5px;
            font-size: 14px;
            background-color: #333;
            color: white;
            border: 1px solid #444;
            cursor: pointer;
            border-radius: 5px;
            font-family: Poppins, sans-serif;
            font-weight: 400;
            transition: all .3s ease;
        }

        #answerToggle:hover, #refreshButton:hover {
            background-color: #444;
            transition: all .3s ease;
        }

        #answerContainer {
            display: block;
        }

        #restoreButton {
            display: none;
            position: absolute;
            top: 5px;
            right: 5px;
            background-color: #333;
            color: white;
            border: none;
            border-radius: 5px;
            font-size: 12px;
            padding: 5px;
            cursor: pointer;
            font-family: Poppins, sans-serif;
        }

        #restoreButton.minimized {
            display: block;
        }

        img.img-ans {
            max-width: 100%;
            height: auto;
            border-radius: 5px;
            margin-top: 10px;
        }
    `;
    document.head.appendChild(style);

    // Add header, buttons, and answer container
    const header = document.createElement('h1');
    header.id = 'khanHackHeader';
    header.innerHTML = 'Khan<span>Hack</span>';

    const answerToggle = document.createElement('button');
    answerToggle.id = 'answerToggle';
    answerToggle.textContent = 'Hide Answers';
    let answersVisible = true;

    const refreshButton = document.createElement('button');
    refreshButton.id = 'refreshButton';
    refreshButton.textContent = 'Reset Answer List';

    const minimizeButton = document.createElement('button');
    minimizeButton.id = 'minimizeButton';
    minimizeButton.textContent = 'Minimize';

    const restoreButton = document.createElement('button');
    restoreButton.id = 'restoreButton';
    restoreButton.textContent = 'Restore';

    const answerContainer = document.createElement('div');
    answerContainer.id = 'answerContainer';

    mainContainer.appendChild(minimizeButton);
    mainContainer.appendChild(header);
    mainContainer.appendChild(answerToggle);
    mainContainer.appendChild(refreshButton);
    mainContainer.appendChild(answerContainer);
    mainContainer.appendChild(restoreButton);

    // Add functionality to hide/unhide the answers
    answerToggle.addEventListener('click', () => {
        answersVisible = !answersVisible;
        answerContainer.style.display = answersVisible ? 'block' : 'none';
        answerToggle.textContent = answersVisible ? 'Hide Answers' : 'Unhide Answers';
    });

    // Add functionality to refresh the page
    refreshButton.addEventListener('click', () => {
        location.reload();
    });

    // Add functionality to minimize the entire container
    minimizeButton.addEventListener('click', () => {
        mainContainer.classList.add('minimized');
        restoreButton.classList.add('minimized');
        minimizeButton.style.display = 'none';
    });

    // Add functionality to restore the container
    restoreButton.addEventListener('click', () => {
        mainContainer.classList.remove('minimized');
        restoreButton.classList.remove('minimized');
        minimizeButton.style.display = 'block';
    });

    // Utility function to convert decimal to simplified fraction
    function decimalToFraction(decimal) {
        if (decimal === 0) return "0";

        let negative = decimal < 0;
        decimal = Math.abs(decimal); // Make it positive to simplify calculations

        let whole = Math.trunc(decimal);
        let fraction = decimal - whole;

        const gcd = (a, b) => b ? gcd(b, a % b) : a;
        const precision = 1000000;
        let numerator = Math.round(fraction * precision);
        let denominator = precision;

        let commonDenominator = gcd(numerator, denominator);
        numerator = numerator / commonDenominator;
        denominator = denominator / commonDenominator;

        let fractionString = numerator === 0 ? '' : `${Math.abs(numerator)}/${denominator}`;
        let result = whole !== 0 ? `${whole} ${fractionString}`.trim() : fractionString;

        if (negative && result) {
            result = `-${result}`;
        }

        if (whole === 0 && numerator !== 0) {
            result = `${negative ? '-' : ''}${numerator}/${denominator}`;
        }

        return result;
    }

    // Function to handle image replacement
    function replaceGraphieImage(content) {
        const imageRegex = /!\[.*?\]\(web\+graphie:\/\/cdn.kastatic.org\/ka-perseus-graphie\/([a-f0-9]+)\)/;
        const match = content.match(imageRegex);
        if (match && match[1]) {
            const imageId = match[1];
            return `<img src="https://cdn.kastatic.org/ka-perseus-graphie/${imageId}.svg" class="img-ans" />`;
        }
        return content;
    }

    // Function to append content to the main container
    function appendToGUI(content, questionType) {
        try {
            const div = document.createElement('div');
            div.classList.add('question-div');

            // Add specific class based on question type
            if (questionType === 'radio') {
                div.classList.add('radio-div');
            } else if (questionType === 'expression') {
                div.classList.add('expression-div');
            } else if (questionType === 'dropdown') {
                div.classList.add('dropdown-div');
            } else if (questionType === 'orderer') {
                div.classList.add('orderer-div');
            } else if (questionType === 'input') {
                div.classList.add('input-div');
            } else if (questionType === 'plotter') {
                div.classList.add('plotter-div');
            } else if (questionType === 'no-support') {
                div.classList.add('no-support-div');
            }

            // Replace image markdown with <img> tag
            content = replaceGraphieImage(content);

            div.innerHTML = content; // Use innerHTML to allow image rendering
            answerContainer.appendChild(div); // Append answers to the answer container
        } catch (error) {
            console.error("Failed to append content to GUI:", error);
        }
    }

    // Function to clean up LaTeX-like expressions
    function cleanLatexExpression(expr) {
        if (typeof expr !== 'string') return expr; // Ensure it's a string before processing
        return expr.replace(/\\dfrac{(.+?)}{(.+?)}/g, '$1/$2')  // Fractions
                   .replace(/\\frac{(.+?)}{(.+?)}/g, '$1/$2')   // Fractions
                   .replace(/\\dfrac(\d+)(\d+)/g, '$1/$2')      // Handle shorthand \\dfrac12 as 1/2
                   .replace(/\\frac(\d+)(\d+)/g, '$1/$2')       // Handle shorthand \\frac12 as 1/2
                   .replace(/\\left\(/g, '(')
                   .replace(/\\right\)/g, ')')
                   .replace(/\\cdot/g, '*')
                   .replace(/\\times/g, '*')
                   .replace(/\\div/g, '/')
                   .replace(/\\\\/g, '')
                   .replace(/\\,/g, '')
                   .replace(/\\sqrt{(.+?)}(.*?)/g, '√($1)')
                   .replace(/\\sqrt/g, '√')
                   .replace(/\\cos/g, 'cos')                   // Handle cosine function
                   .replace(/\\sin/g, 'sin')                   // Handle sine function (in case you encounter it)
                   .replace(/\\tan/g, 'tan')                   // Handle tangent function (in case you encounter it)
                   .replace(/\\degree/g, '°')                  // Handle degree symbol
                   .replace(/\\,/g, '')
                   .replace(/\\\[/g, '[')
                   .replace(/\\\]/g, ']')
                   .replace(/\$/g, '');                        // Remove dollar signs
    }

    // Override fetch to capture /getAssessmentItem response
    window.fetch = function() {
        return originalFetch.apply(this, arguments).then(async (response) => {
            try {
                // Only handle responses that contain assessment items (questions/answers)
                if (response.url.includes("/getAssessmentItem")) {
                    const clonedResponse = response.clone();
                    const jsonData = await clonedResponse.json();
                    const itemData = jsonData.data.assessmentItem.item.itemData;
                    const questionData = JSON.parse(itemData).question;

                    // Log the full question data for debugging
                    console.log('Full question data:', questionData);

                    // Initialize a variable to hold the combined answers for dropdowns per question
                    const combinedAnswersPerQuestion = {};
                    let numericInputAnswers = [];

                    // Iterate over each widget and handle the answer logic
                    Object.keys(questionData.widgets).forEach(widgetKey => {
                        const widget = questionData.widgets[widgetKey];
                        let answer = "No answer available";

                        // Log the entire widget for debugging
                        console.log('Widget data:', widget);

                        try {
                            // Handle numeric-input type questions
                            if (widget.type === "input-number" || widget.type === "numeric-input") {
                                let answer;

                                // Check different possible paths for the answer, ensuring 0 is treated as a valid answer
                                if (widget.options?.value !== undefined && widget.options?.value !== null) {
                                    answer = widget.options.value;
                                } else if (widget.options?.answers?.[0]?.value !== undefined && widget.options?.answers?.[0]?.value !== null) {
                                    answer = widget.options.answers[0].value;
                                }

                                // If the answer is found, push it to the numericInputAnswers array
                                if (answer !== undefined && answer !== null) {
                                    numericInputAnswers.push(answer); // Add the answer to the array
                                } else {
                                    console.error("Answer not found for widget:", widget);
                                }
                            }

                            // Handle graphing type questions
                            else if (widget.type === "grapher" || widget.type === "interactive-graph") {
                                if (widget.options?.correct?.coords && widget.options.correct.coords.length > 0) {
                                    const coords = widget.options.correct.coords.map(coord => `(${coord.join(", ")})`);
                                    appendToGUI(`Graphing Question: Correct Coordinates: ${coords.join(" and ")}`, 'plotter');
                                    console.log('Graphing answers (coordinates):', coords);
                                } else {
                                    appendToGUI(`Graphing Question: No valid coordinates found`, 'plotter');
                                    console.log('Graphing Question: No valid coordinates found');
                                }
                            }

                            // Handle unsupported marker type questions
                            else if (widget.type === "label-image") {
                                appendToGUI("No hack support for this problem", 'no-support');
                            }

                            // Handle intercept type questions
                            else if (widget.type === "numeric-input" && widget.options?.answers) {
                                const yIntercept = `(0, ${widget.options.answers[0].value})`;
                                const xIntercept = `(${widget.options.answers[1].value}, 0)`;
                                appendToGUI(`y-intercept: ${yIntercept}, x-intercept: ${xIntercept}`, 'input');
                            }

                            // Handle dropdown type questions
                            else if (widget.type === "dropdown") {
                                if (widget.options?.choices) {
                                    answer = widget.options.choices.filter(c => c.correct).map(c => cleanLatexExpression(c.content));
                                }
                                const questionContent = questionData.content;
                                if (!combinedAnswersPerQuestion[questionContent]) {
                                    combinedAnswersPerQuestion[questionContent] = [];
                                }
                                combinedAnswersPerQuestion[questionContent].push(...answer);
                            }

                            // Handle expression type questions
                            else if (widget.type === "expression") {
                                if (widget.options?.answerForms && widget.options.answerForms.length > 1) {
                                    answer = widget.options.answerForms.filter(af => af.considered === "correct").map(af => cleanLatexExpression(af.value));
                                    appendToGUI(`Any of the following: ${JSON.stringify(answer, null, 2)}`, 'expression');
                                } else {
                                    answer = widget.options.answerForms.filter(af => af.considered === "correct").map(af => cleanLatexExpression(af.value));
                                    appendToGUI(`Question Type: expression, Answer: ${JSON.stringify(answer, null, 2)}`, 'expression');
                                }
                                console.log('Expression answers:', answer);
                            }

                            // Handle orderer type questions
                            else if (widget.type === "orderer") {
                                if (widget.options?.correctOptions) {
                                    const correctOrder = widget.options.correctOptions.map(option => option.content);
                                    appendToGUI(`Orderer Question: Correct Order: ${JSON.stringify(correctOrder, null, 2)}`, 'orderer');
                                    console.log('Orderer answers:', answer);
                                }
                            }

                            // Handle radio type questions
                            else if (widget.type === "radio") {
                                if (widget.options?.choices) {
                                    const correctChoices = widget.options.choices.filter(c => c.correct).map(c => cleanLatexExpression(c.content || "None of the above"));
                                    answer = correctChoices;
                                    answer = answer.map(choice => replaceGraphieImage(choice)); // Replace image markdown
                                }
                                appendToGUI(`Question Type: radio, Answer: ${answer.join(', ')}`, 'radio');
                                console.log('Radio answers:', answer);
                            }

                            // Handle plotter type questions
                            else if (widget.type === "plotter") {
                                const correctAnswers = widget.options?.correct || [];
                                appendToGUI(`Data Plot Locations in Order: Answers: ${correctAnswers.join(", ")}`, 'plotter');
                                console.log('Plotter correct answers:', correctAnswers);
                            }

                        } catch (innerError) {
                            console.error("Error processing widget:", widget, innerError);
                        }
                    });

                    if (numericInputAnswers.length > 0) {
                        appendToGUI(`Numeric Input Question: Correct Answer(s): [${numericInputAnswers.join(', ')}]`, 'input');
                        console.log(`Numeric Input Answers: [${numericInputAnswers.join(', ')}]`);
                    }

                    // Display combined dropdown answers at once after all widgets are processed
                    Object.keys(combinedAnswersPerQuestion).forEach(questionContent => {
                        const finalCombinedAnswers = combinedAnswersPerQuestion[questionContent];
                        appendToGUI(`Combined Answers: ${JSON.stringify(finalCombinedAnswers, null, 2)}`, 'dropdown');
                        console.log('Dropdown combined answers:', finalCombinedAnswers);
                    });

                }
                return response;
            } catch (error) {
                console.error('Failed to fetch assessment data:', error);
            }
        }).catch((error) => {
            console.error("Network or fetch error:", error);
        });
    };
})();
})();