KhanHack

Khan Academy Answer Hack

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         KhanHack
// @namespace    https://greasyfork.org/users/783447
// @version      7.1
// @description  Khan Academy Answer Hack
// @author       Logzilla6
// @match        https://*.khanacademy.org/*
// @icon         https://i.ibb.co/K5g1KMq/Untitled-drawing-3.png
// ==/UserScript==

//ALL FOLLOWING CODE IS UNDER THE KHANHACK TRADEMARK. UNAUTHORIZED DISTRIBUTION CAN/WILL RESULT IN LEGAL ACTION

//Note that KhanHack™ is an independent initiative and is not affiliated with or endorsed by Khan Academy. We respect the work of Khan Academy and its mission to provide free education, but KhanHack™ operates separately with its own unique goals.

let answerQueue = [];
let currentAnswerIndex = 0;
let isGhostMode = false;
let waitForData = false;

const createMenu = () => {
    const style = document.createElement('style');
    style.innerHTML = `
        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&display=swap');

        #khanhack-menu {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 320px;
            height: 420px;
            background-color: #123576;
            border: 3px solid #07152e;
            border-radius: 16px;
            padding: 0;
            color: white;
            font-family: 'Noto Sans', sans-serif;
            z-index: 99999;
            box-shadow: 0 10px 25px rgba(0,0,0,0.5);
            transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            font-size: 14px;
            opacity: 1;
        }

        #khanhack-menu.ghost-active {
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        #khanhack-menu.ghost-active:hover {
            opacity: 1;
        }

        #khanhack-menu.minimized {
            width: 48px;
            height: 48px;
            border-radius: 50%;
            cursor: pointer;
            padding: 0;
            justify-content: center;
            align-items: center;
            opacity: 0.8;
            border: 2px solid #07152e;
        }

        #khanhack-menu.minimized:hover {
            opacity: 1;
            transform: scale(1.05);
        }

        /* Header Section */
        .kh-header {
            display: flex;
            align-items: center;
            padding: 12px 16px;
            background-color: rgba(7, 21, 46, 0.3);
            border-bottom: 1px solid rgba(255,255,255,0.1);
        }

        .kh-header-title {
            font-weight: 700;
            font-size: 18px;
            letter-spacing: 0.5px;
            color: #fff;
            display: flex;
            align-items: center;
            gap: 8px;
            flex-grow: 1;
        }

        .kh-controls {
            display: flex;
            gap: 12px;
            align-items: center;
        }

        .kh-icon-btn {
            width: 24px;
            height: 24px;
            cursor: pointer;
            opacity: 0.8;
            transition: opacity 0.2s;
        }

        .kh-icon-btn:hover {
            opacity: 1;
        }

        .kh-content {
            flex: 1;
            padding: 15px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 10px;
            scrollbar-width: thin;
            scrollbar-color: rgba(255,255,255,0.3) transparent;
        }

        .kh-content::-webkit-scrollbar {
            width: 6px;
        }

        .kh-content::-webkit-scrollbar-thumb {
            background-color: rgba(255,255,255,0.3);
            border-radius: 3px;
        }

        .kh-placeholder {
            text-align: center;
            color: rgba(255,255,255,0.6);
            margin-top: 50%;
            transform: translateY(-50%);
            font-style: italic;
        }

        .kh-card {
            background: #f0f0f0;
            color: #1a1a1a;
            padding: 12px;
            border-radius: 8px;
            border-left: 5px solid #2967d9;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            word-wrap: break-word;
            font-weight: 500;
            font-size: 15px;
            animation: fadeIn 0.3s ease;
            margin-bottom: 8px;
            cursor: pointer;
            transition: transform 0.1s;
        }

        .kh-card:active {
            transform: scale(0.98);
        }

        .kh-card img {
            max-width: 100%;
            border-radius: 4px;
            margin-top: 5px;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(5px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .kh-footer {
            padding: 10px 15px;
            background-color: rgba(7, 21, 46, 0.5);
            border-top: 1px solid rgba(255,255,255,0.1);
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .kh-nav-btn {
            background: #2967d9;
            border: none;
            color: white;
            padding: 6px 12px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            font-size: 12px;
            transition: background 0.2s;
        }

        .kh-nav-btn:hover {
            background: #3b7ced;
        }

        .kh-nav-btn:disabled {
            background: #1e3a6e;
            color: #aaa;
            cursor: not-allowed;
        }

        .kh-status {
            font-size: 12px;
            color: rgba(255,255,255,0.7);
        }

        .kh-toggle {
            display: none;
            width: 24px;
            height: 24px;
            filter: brightness(0) invert(1);
        }

        .kh-settings-view {
            display: none;
            flex-direction: column;
            align-items: center;
            gap: 15px;
            padding-top: 20px;
            animation: fadeIn 0.3s ease;
        }

        .kh-setting-item {
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 16px;
        }

        .kh-beta-tag {
            background: #2967d9;
            padding: 2px 6px;
            border-radius: 4px;
            font-size: 10px;
            font-weight: bold;
        }

        .kh-toggle-switch {
            position: relative;
            display: inline-block;
            width: 40px;
            height: 20px;
        }

        .kh-toggle-switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .kh-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #07152e;
            transition: .4s;
            border-radius: 20px;
            border: 1px solid white;
        }

        .kh-slider:before {
            position: absolute;
            content: "";
            height: 14px;
            width: 14px;
            left: 3px;
            bottom: 2px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }

        input:checked + .kh-slider {
            background-color: #2967d9;
        }

        input:checked + .kh-slider:before {
            transform: translateX(20px);
        }

        #khanhack-menu.minimized .kh-header,
        #khanhack-menu.minimized .kh-content,
        #khanhack-menu.minimized .kh-footer {
            display: none;
        }

        #khanhack-menu.minimized .kh-toggle {
            display: block;
        }

        .kh-hidden {
            display: none !important;
        }
    `;
    document.head.appendChild(style);

    const menu = document.createElement('div');
    menu.id = 'khanhack-menu';

    const toggleIcon = document.createElement('img');
    toggleIcon.src = 'https://i.ibb.co/RpqPcR1/hamburger.png';
    toggleIcon.className = 'kh-toggle';

    const header = document.createElement('div');
    header.className = 'kh-header';

    const discordIcon = document.createElement('img');
    discordIcon.src = 'https://i.ibb.co/grF973h/discord.png';
    discordIcon.className = 'kh-icon-btn';
    discordIcon.style.marginRight = '10px';
    discordIcon.onclick = () => window.open('https://discord.gg/7xWK5n9Zbp', '_blank');

    const title = document.createElement('div');
    title.id = 'kh-title-text';
    title.className = 'kh-header-title';
    title.innerText = 'KhanHack';

    const controls = document.createElement('div');
    controls.className = 'kh-controls';

    const settingsIcon = document.createElement('img');
    settingsIcon.src = 'https://i.ibb.co/q0QVKGG/gearicon.png';
    settingsIcon.className = 'kh-icon-btn';
    settingsIcon.title = 'Settings';

    const collapseBtn = document.createElement('span');
    collapseBtn.innerHTML = '−';
    collapseBtn.className = 'kh-icon-btn';
    collapseBtn.style.fontSize = '24px';
    collapseBtn.style.lineHeight = '20px';
    collapseBtn.style.fontWeight = 'bold';
    collapseBtn.style.textAlign = 'center';
    collapseBtn.title = 'Minimize';
    collapseBtn.onclick = (e) => {
        e.stopPropagation();
        toggleMenu();
    };

    controls.appendChild(settingsIcon);
    controls.appendChild(collapseBtn);

    header.appendChild(discordIcon);
    header.appendChild(title);
    header.appendChild(controls);

    const content = document.createElement('div');
    content.className = 'kh-content';

    const answerView = document.createElement('div');
    answerView.id = 'kh-answer-view';

    const placeholder = document.createElement('div');
    placeholder.className = 'kh-placeholder';
    placeholder.id = 'kh-placeholder-text';
    placeholder.innerText = 'Waiting for questions...';

    const contentBox = document.createElement('div');
    contentBox.id = 'kh-content-box';

    answerView.appendChild(placeholder);
    answerView.appendChild(contentBox);

    const settingsView = document.createElement('div');
    settingsView.className = 'kh-settings-view';
    settingsView.id = 'kh-settings-view';

    const ghostItem = document.createElement('div');
    ghostItem.className = 'kh-setting-item';

    const ghostLabel = document.createElement('span');
    ghostLabel.innerText = "Ghost Mode:";

    const ghostSwitch = document.createElement('label');
    ghostSwitch.className = 'kh-toggle-switch';
    const ghostInput = document.createElement('input');
    ghostInput.type = 'checkbox';
    ghostInput.onchange = (e) => toggleGhostMode(e.target.checked);
    const ghostSlider = document.createElement('span');
    ghostSlider.className = 'kh-slider';

    ghostSwitch.appendChild(ghostInput);
    ghostSwitch.appendChild(ghostSlider);
    ghostItem.appendChild(ghostLabel);
    ghostItem.appendChild(ghostSwitch);

    const autoAnsItem = document.createElement('div');
    autoAnsItem.className = 'kh-setting-item';
    autoAnsItem.innerHTML = 'Auto Answer <span class="kh-beta-tag">BETA</span>';

    const pointFarmerItem = document.createElement('div');
    pointFarmerItem.className = 'kh-setting-item';
    pointFarmerItem.innerHTML = 'Point Farmer <span class="kh-beta-tag">BETA</span>';

    const versionText = document.createElement('div');
    versionText.style.marginTop = 'auto';
    versionText.style.opacity = '0.5';
    versionText.style.fontSize = '12px';
    versionText.innerText = 'KhanHack™ | 7.1';

    settingsView.appendChild(ghostItem);
    settingsView.appendChild(autoAnsItem);
    settingsView.appendChild(pointFarmerItem);
    settingsView.appendChild(versionText);

    content.appendChild(answerView);
    content.appendChild(settingsView);

    const footer = document.createElement('div');
    footer.className = 'kh-footer';
    footer.id = 'kh-footer';

    const prevBtn = document.createElement('button');
    prevBtn.className = 'kh-nav-btn';
    prevBtn.innerText = '◄ Prev';
    prevBtn.id = 'kh-prev-btn';
    prevBtn.onclick = () => navigateAnswer(-1);

    const statusText = document.createElement('span');
    statusText.className = 'kh-status';
    statusText.id = 'kh-status-text';
    statusText.innerText = '0 / 0';

    const nextBtn = document.createElement('button');
    nextBtn.className = 'kh-nav-btn';
    nextBtn.innerText = 'Next ►';
    nextBtn.id = 'kh-next-btn';
    nextBtn.onclick = () => navigateAnswer(1);

    footer.appendChild(prevBtn);
    footer.appendChild(statusText);
    footer.appendChild(nextBtn);

    menu.appendChild(toggleIcon);
    menu.appendChild(header);
    menu.appendChild(content);
    menu.appendChild(footer);
    document.body.appendChild(menu);

    let isMinimized = false;
    let isSettingsOpen = false;

    settingsIcon.onclick = () => {
        isSettingsOpen = !isSettingsOpen;
        if (isSettingsOpen) {
            answerView.classList.add('kh-hidden');
            footer.classList.add('kh-hidden');
            settingsView.style.display = 'flex';
            title.innerText = "Settings";
            discordIcon.classList.add('kh-hidden');
            settingsIcon.style.opacity = '1';
        } else {
            answerView.classList.remove('kh-hidden');
            footer.classList.remove('kh-hidden');
            settingsView.style.display = 'none';
            title.innerText = "KhanHack";
            discordIcon.classList.remove('kh-hidden');
        }
    };

    const toggleMenu = () => {
        isMinimized = !isMinimized;
        if (isMinimized) {
            menu.classList.add('minimized');
        } else {
            menu.classList.remove('minimized');
        }
    };

    const toggleGhostMode = (active) => {
        isGhostMode = active;
        if (active) {
            menu.classList.add('ghost-active');
        } else {
            menu.classList.remove('ghost-active');
        }
    };

    menu.addEventListener('click', (e) => {
        if (isMinimized) toggleMenu();
    });

    updateNavState();
};

const updateMenuContent = () => {
    const contentBox = document.getElementById('kh-content-box');
    const placeholder = document.getElementById('kh-placeholder-text');

    if (answerQueue.length === 0) {
        if(placeholder) placeholder.style.display = 'block';
        return;
    }

    if(placeholder) placeholder.style.display = 'none';

    contentBox.innerHTML = '';

    const currentAnsArray = answerQueue[currentAnswerIndex];

    if (currentAnsArray && Array.isArray(currentAnsArray)) {
        currentAnsArray.forEach(rawAns => {
            const currentAns = String(rawAns);

            const card = document.createElement('div');
            card.className = 'kh-card';

            if (currentAns.startsWith('[Image]')) {
                const parts = currentAns.match(/\[Image\] (.*)\((.*)\)/);
                if (parts) {
                    if (parts[1].trim()) {
                        const textP = document.createElement('div');
                        textP.innerText = parts[1].trim();
                        card.appendChild(textP);
                    }
                    const img = document.createElement('img');
                    img.src = parts[2];
                    card.appendChild(img);
                } else {
                    card.innerText = currentAns;
                }
            } else {
                card.innerText = currentAns;
            }

            card.title = "Click to copy";
            card.onclick = () => {
                navigator.clipboard.writeText(currentAns.replace(/\[Image\] .*/, 'Image copied'));

                const originalBg = card.style.backgroundColor;
                card.style.backgroundColor = '#d1e7dd';

                setTimeout(() => {
                    card.style.backgroundColor = originalBg;
                }, 200);
            };

            contentBox.appendChild(card);
        });
    }

    updateNavState();
};

const updateNavState = () => {
    const prevBtn = document.getElementById('kh-prev-btn');
    const nextBtn = document.getElementById('kh-next-btn');
    const statusText = document.getElementById('kh-status-text');

    if (prevBtn && nextBtn && statusText) {
        statusText.innerText = `${answerQueue.length > 0 ? currentAnswerIndex + 1 : 0} / ${answerQueue.length}`;

        prevBtn.disabled = currentAnswerIndex <= 0;
        nextBtn.disabled = currentAnswerIndex >= answerQueue.length - 1;
    }
};

const navigateAnswer = (direction) => {
    const newIndex = currentAnswerIndex + direction;
    if (newIndex >= 0 && newIndex < answerQueue.length) {
        currentAnswerIndex = newIndex;
        updateMenuContent();
    }
};

const addAnswerToQueue = (answerGroup) => {
    answerQueue.push(answerGroup);

    if (answerQueue.length === 1) {
        currentAnswerIndex = 0;
        updateMenuContent();
        waitForData = false;
    } else if (waitForData) {
        navigateAnswer(1);
        waitForData = false;
    } else {
        updateNavState();
    }
};

const setupAutoAdvance = () => {
    document.addEventListener('click', (e) => {
        const target = e.target.closest('button');
        if (!target) return;

        const testId = target.getAttribute('data-testid');
        const ariaLabel = target.getAttribute('aria-label');
        const text = target.innerText || '';
        if (
            testId === 'exercise-next-question-button' ||
            (ariaLabel && ariaLabel.includes('Next question')) ||
            text.includes('Next question')
        ) {
            setTimeout(() => {
                navigateAnswer(1);
            }, 200);
        }
    });

    document.addEventListener('keyup', (e) => {
        if (e.key === 'Enter') {
            waitForData = true;
        }
    });
};

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
        createMenu();
        setupAutoAdvance();
    });
} else {
    createMenu();
    setupAutoAdvance();
}

let originalJson = JSON.parse;

JSON.parse = function (jsonString) {
    let parsedData = originalJson(jsonString);

    try {
        if (parsedData.data) {
            let assessmentItem = parsedData.data.assessmentItemById || parsedData.data.assessmentItemByProblemNumber;

            if (assessmentItem && assessmentItem.item) {
                let itemData = JSON.parse(assessmentItem.item.itemData);
                let hasGradedWidget = Object.values(itemData.question.widgets).some(widget => widget.graded === true);

                if (hasGradedWidget) {
                    const currentItemAnswers = [];

                    for (let widgetKey in itemData.question.widgets) {
                        let widget = itemData.question.widgets[widgetKey];

                        if (widgetHandlers[widget.type]) {
                            const answer = widgetHandlers[widget.type](widget);
                            if (answer !== undefined && answer !== null) {
                                if (Array.isArray(answer)) {
                                    currentItemAnswers.push(...answer);
                                } else {
                                    currentItemAnswers.push(answer);
                                }
                            }
                        }
                    }

                    if (currentItemAnswers.length > 0) {
                         addAnswerToQueue(currentItemAnswers);
                    }
                }
            }
        }
    } catch (error) {
    }

    return parsedData;
};

const cleanLatex = (text) => {
    if (typeof text !== 'string') return text;
    return text
        .replace(/\\begin{align}/g, '\\begin{aligned}')
        .replace(/\\end{align}/g, '\\end{aligned}')
        .replace(/\$/g, '');
};

const widgetHandlers = {
    "numeric-input": (w) => w.options.answers[0].value,

    "input-number": (w) => w.options.value,

    "dropdown": (w) => w.options.choices.find(c => c.correct)?.content,

    "expression": (w) => cleanLatex(w.options.answerForms[0].value),

    "matcher": (w) => cleanLatex(w.options.right),

    "grapher": (w) => w.options.correct.coords.join(' | '),

    "interactive-graph": (w) => {
        return w.options.correct.coords
            .filter(c => c !== undefined)
            .join(' | ');
    },

    "categorizer": (w) => {
        return w.options.values
            .map(v => w.options.categories[v])
            .join(", ");
    },

    "matrix": (w) => w.options.answers.join(' | '),

    "label-image": (w) => {
        const markers = w.options.markers;
        return markers
            .filter(m => m.answers)
            .map((m, i) => {
                const label = m.label ? m.label.replace('Point ', '').replace(/\./g, '').trim() : '';
                const cleanAns = cleanLatex(m.answers.toString());
                return label ? `${label}: ${cleanAns}` : cleanAns;
            })
            .join(" | ");
    },

    "radio": (w) => {
        const choices = w.options.choices;
        const isNone = choices.some(c => c.isNoneOfTheAbove && c.correct);
        if (isNone) return "None of the above";

        return choices
            .filter(c => c.correct)
            .flatMap(c => {
                const content = c.content;
                const imageMatch = content.match(/!\[([^\]]*)\]\(([^)]+)\)/);

                if (imageMatch) {
                    let text = imageMatch[1];
                    let url = imageMatch[2];

                    if (url.includes('web+graphie')) {
                        url = url.replace('web+graphie:', 'https:') + '.svg';
                    }

                    const result = [`[Image] (${url})`];
                    if (text && text.trim().length > 0) {
                        result.push(text.trim());
                    }
                    return result;
                }

                return cleanLatex(content);
            });
    }
};