Greasy Fork

来自缓存

Greasy Fork is available in English.

K-Bot | Kahoot Answer Viewer & Auto-Answer Cheat Mod Menu (Working 2026)

A minimalist Kahoot mod menu. Features a live answer viewer and automated responding.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         K-Bot | Kahoot Answer Viewer & Auto-Answer Cheat Mod Menu (Working 2026) 
// @version      2.1
// @namespace    juanbolsa
// @description  A minimalist Kahoot mod menu. Features a live answer viewer and automated responding.
// @author       juanbolsa
// @match        https://kahoot.it/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=kahoot.it
// @license      MIT
// @grant        none
// ==/UserScript==
 
(function() {
    'use strict';
 
    // ─── Config ───────────────────────────────────────────────────────────────────
    const VERSION = '2.1';
    const MULTI_SELECT_STAGGER_MS = 60;
 
    // ─── State ────────────────────────────────────────────────────────────────────
    const state = {
        questions: [],
        numQuestions: 0,
        questionNum: -1,
        lastAnsweredQuestion: -1,
        baseDelay: 0,
        randomDelay: 0,
        autoAnswer: false,
        showAnswers: false,
    };
 
    // Keep track of DOM values to avoid redundant updates
    let lastProcessedQuestionText = "";
 
    // ─── DOM Helpers ──────────────────────────────────────────────────────────────
    function queryBySelector(value, tag = '*') {
        return document.querySelector(`${tag}[data-functional-selector="${value}"]`);
    }
 
    function createElement(tag, { styles = {}, className, text } = {}) {
        const el = document.createElement(tag);
        if (className) el.className = className;
        if (text)      el.textContent = text;
        Object.assign(el.style, styles);
        return el;
    }
 
    // ─── Inject Assets ────────────────────────────────────────────────────────────
    const fontLink = document.createElement('link');
    fontLink.rel  = 'stylesheet';
    fontLink.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap';
    document.head.appendChild(fontLink);
 
    const globalStyle = document.createElement('style');
globalStyle.textContent = `
        .kb-ui * {
            box-sizing: border-box;
            font-family: 'Inter', system-ui, -apple-system, sans-serif;
        }
        .kb-ui button, .kb-input, .kb-switch-track::before {
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .kb-ui {
            /* 85% Opacity Backgrounds */
            --bg: rgba(15, 15, 15, 0.75);
            --surface: rgba(24, 24, 24, 0.75);
            --border: rgba(255, 255, 255, 0.1);
            --accent: #f0f0f0;
            --muted: #888;
            --green: #4caf74;
            --red: #c0392b;
            --text: #d8d8d8;
            --label: #aaa;
 
            /* Frosted Glass Effect */
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5), 0 0 1px rgba(255, 255, 255, 0.1);
        }
        .kb-handle {
            cursor: grab;
            user-select: none;
            background: var(--surface);
            border-bottom: 1px solid var(--border);
        }
        .kb-handle:active { cursor: grabbing; }
 
        .kb-input {
            width: 100% !important;
            background-color: rgba(0, 0, 0, 0.3) !important;
            border: 1px solid var(--border) !important;
            color: #ffffff !important;
            border-radius: 4px;
            font-size: 13px;
            font-weight: 600;
            padding: 5px 8px;
            outline: none;
            transition: all 0.2s ease;
            margin-top: 8px;
        }
        .kb-input:focus { border-color: rgba(255, 255, 255, 0.4) !important; }
        .kb-input.ok  { border-color: var(--green) !important; background: rgba(13, 46, 26, 0.6) !important; }
        .kb-input.err { border-color: var(--red) !important; background: rgba(46, 13, 13, 0.6) !important; }
 
        .kb-slider-wrap  { display: flex; flex-direction: column; gap: 3px; }
        .kb-slider-label { display: flex; justify-content: space-between; margin-bottom: 2px; }
        .kb-slider-label span { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
 
        .kb-range {
            -webkit-appearance: none;
            width: 100%;
            height: 4px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 2px;
            outline: none;
        }
        .kb-range::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 14px;
            height: 14px;
            background: var(--accent);
            border-radius: 50%;
            cursor: pointer;
            box-shadow: 0 0 10px rgba(0,0,0,0.5);
        }
 
        .kb-toggle-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 6px 0;
            border-bottom: 1px solid var(--border);
        }
        .kb-toggle-label { color: var(--text); font-size: 13px; font-weight: 600; }
 
        .kb-switch { position: relative; width: 34px; height: 18px; }
        .kb-switch input { opacity: 0; width: 0; height: 0; }
        .kb-switch-track {
            position: absolute;
            inset: 0;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 20px;
            cursor: pointer;
            transition: 0.3s;
        }
        .kb-switch-track::before {
            content: '';
            position: absolute;
            width: 12px;
            height: 12px;
            left: 3px;
            top: 3px;
            background: #777;
            border-radius: 50%;
            transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .kb-switch input:checked + .kb-switch-track { background: rgba(76, 175, 116, 0.3); }
        .kb-switch input:checked + .kb-switch-track::before {
            transform: translateX(16px);
            background: var(--green);
            box-shadow: 0 0 8px var(--green);
        }
 
        .kb-divider { border: none; border-top: 1px solid var(--border); margin: 8px 0; }
        .kb-section-title {
            color: var(--muted);
            font-size: 12px;
            font-weight: 800;
            text-transform: uppercase;
            letter-spacing: 0.1em;
            margin-bottom: 3px;
            text-align: center;
        }
        .kb-stat { color: var(--accent); font-size: 18px; font-weight: 700; text-align: center; margin-top: 15px;}
        .kb-credit { color: #666; font-size: 13px; text-align: center; margin-top: 2px; font-weight: 600; }
    `;
    document.head.appendChild(globalStyle);
 
    // ─── UI Construction ──────────────────────────────────────────────────────────
    const uiElement = createElement('div', {
        className: 'kb-ui',
        styles: { position: 'fixed', top: '5%', left: '5%', width: '240px', background: 'var(--bg)', border: '1px solid var(--border)', borderRadius: '6px', boxShadow: '0 8px 32px rgba(0,0,0,0.7)', zIndex: '9999' }
    });
 
    const handle = createElement('div', {
        className: 'kb-handle',
        styles: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 8px', background: 'var(--surface)', borderBottom: '1px solid var(--border)' }
    });
 
    handle.append(createElement('span', { text: 'K-Bot v'+VERSION, styles: { color: 'var(--accent)', fontSize: '13px', fontWeight: '700' } }));
 
    const titleControls = createElement('div', { styles: { display: 'flex', gap: '5px' } });
    const minimizeButton = createElement('button', { text: '-', styles: { background: '#888', border: 'none', width: '22px', height: '20px', cursor: 'pointer', borderRadius: '3px', color: '#fff', fontWeight: '800',} });
    const closeButton = createElement('button', { text: '✕', styles: { background: '#c0392b', color: '#fff', border: 'none', width: '22px', height: '20px', cursor: 'pointer', borderRadius: '3px', fontWeight: '800',} });
 
    titleControls.append(minimizeButton, closeButton);
    handle.append(titleControls);
    uiElement.appendChild(handle);
 
    const body = createElement('div', { styles: { padding: '15px', display: 'flex', flexDirection: 'column', gap: '6px' } });
    uiElement.appendChild(body);
 
    // Quiz ID Input
    const quizSection = document.createElement('div');
    quizSection.append(createElement('div', { className: 'kb-section-title', text: 'Quiz ID' }));
    const inputBox = createElement('input', { className: 'kb-input' });
    inputBox.placeholder = 'Enter Quiz ID (NOT PIN)';
    quizSection.appendChild(inputBox);
    body.appendChild(quizSection);
    body.appendChild(createElement('hr', { className: 'kb-divider' }));
 
    // Sliders
    function makeSlider(labelText, { min, max, step, value }, onInput) {
        const wrap = createElement('div', { className: 'kb-slider-wrap' });
        const labelRow = createElement('div', { className: 'kb-slider-label' });
        const valSpan = createElement('span', { text: `${value} ms`, styles: { color: 'var(--accent)' } });
        labelRow.append(createElement('span', { text: labelText, styles: { color: 'var(--label)' } }), valSpan);
        const input = createElement('input', { className: 'kb-range' });
        input.type = 'range'; Object.assign(input, { min, max, step, value });
        input.oninput = () => { valSpan.textContent = `${input.value} ms`; onInput(+input.value); };
        wrap.append(labelRow, input);
        return wrap;
    }
 
    body.appendChild(createElement('div', { className: 'kb-section-title', text: 'Delay' }));
    body.appendChild(makeSlider('Base', { min: 0, max: 10000, step: 100, value: state.baseDelay }, v => state.baseDelay = v));
    body.appendChild(makeSlider('Random ±', { min: 0, max: 5000, step: 100, value: state.randomDelay }, v => state.randomDelay = v));
    body.appendChild(createElement('hr', { className: 'kb-divider' }));
 
    // Toggles
    function makeToggle(label, key) {
        const row = createElement('div', { className: 'kb-toggle-row' });
        row.append(createElement('span', { className: 'kb-toggle-label', text: label }));
        const sw = createElement('label', { className: 'kb-switch' });
        const input = document.createElement('input'); input.type = 'checkbox';
        input.onchange = () => state[key] = input.checked;
        sw.append(input, createElement('span', { className: 'kb-switch-track' }));
        row.append(sw);
        return row;
    }
 
    body.appendChild(createElement('div', { className: 'kb-section-title', text: 'Answering' }));
    body.appendChild(makeToggle('Auto Answer', 'autoAnswer'));
    body.appendChild(makeToggle('Show Answers', 'showAnswers'));
 
    const questionsLabel = createElement('div', { className: 'kb-stat', text: 'Question 0 / 0' });
    body.appendChild(questionsLabel);
    body.appendChild(createElement('div', { className: 'kb-credit', text: `v${VERSION} · juanbolsa` }));
    document.body.appendChild(uiElement);
 
    // ─── Logic ────────────────────────────────────────────────────────────────────
 
    function triggerReactClick(btn) {
        const fiberKey = Object.keys(btn).find((k) => k.startsWith('__reactFiber'));
        if (!fiberKey) return;
 
        let fiber = btn[fiberKey];
        while (fiber) {
            const onClick = fiber.memoizedProps?.onClick;
            if (onClick) {
                const nativeEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
                const trusted = new Proxy(nativeEvent, {
                    get(target, prop) {
                        if (prop === 'isTrusted') return true;
                        const val = target[prop];
                        return typeof val === 'function' ? val.bind(target) : val;
                    },
                });
                onClick(trusted);
                return;
            }
            fiber = fiber.return;
        }
    }
 
    function answer(question, delay) {
        const performClick = (idx) => {
            const btn = queryBySelector(`answer-${idx}`, 'button');
            if (btn) triggerReactClick(btn);
        };
 
        if (question.type === 'quiz') {
            setTimeout(() => performClick(question.answers[0]), delay);
        } else if (question.type === 'multiple_select_quiz') {
            question.answers.forEach((ans, i) => {
                setTimeout(() => performClick(ans), delay + (i * MULTI_SELECT_STAGGER_MS));
            });
            setTimeout(() => {
                const submit = queryBySelector('multi-select-submit-button', 'button');
                if (submit) triggerReactClick(submit);
            }, delay + (question.answers.length * MULTI_SELECT_STAGGER_MS));
        }
    }
 
    function highlightAnswers(question) {
        question.answers.forEach(i => {
            const btn = queryBySelector(`answer-${i}`, 'button');
            if (btn) btn.style.backgroundColor = 'rgb(0,255,0)';
        });
        question.incorrectAnswers?.forEach(i => {
            const btn = queryBySelector(`answer-${i}`, 'button');
            if (btn) btn.style.backgroundColor = 'rgb(255,0,0)';
        });
    }
 
    function parseQuestions(raw) {
        return raw.map(q => {
            const p = { type: q.type, time: q.time, answers: [] };
            if (q.choices) {
                p.incorrectAnswers = [];
                q.choices.forEach((c, i) => (c.correct ? p.answers : p.incorrectAnswers).push(i));
            }
            return p;
        });
    }
 
    // ─── The Main Loop (Optimized) ────────────────────────────────────────────────
    function pollFrame() {
        const counterEl = queryBySelector('question-index-counter', 'div');
        const currentText = counterEl ? counterEl.textContent : "";
 
        // DIRTY CHECK: Only run logic if the question number text actually changed
        if (currentText !== lastProcessedQuestionText) {
            lastProcessedQuestionText = currentText;
 
            if (currentText) {
                state.questionNum = parseInt(currentText) - 1;
                questionsLabel.textContent = `Question ${state.questionNum + 1} / ${state.numQuestions}`;
            }
        }
 
        // Check if buttons are ready and we haven't answered this specific question index yet
        const firstBtn = queryBySelector('answer-0', 'button');
        if (firstBtn && state.lastAnsweredQuestion !== state.questionNum) {
            state.lastAnsweredQuestion = state.questionNum;
            const qData = state.questions[state.questionNum];
            if (qData) {
                if (state.showAnswers) highlightAnswers(qData);
                if (state.autoAnswer) answer(qData, state.baseDelay + Math.floor(Math.random() * state.randomDelay));
            }
        }
 
        requestAnimationFrame(pollFrame);
    }
 
    // ─── Event Handlers ──────────────────────────────────────────────────────────
    inputBox.oninput = async () => {
        const id = inputBox.value.trim();
        inputBox.className = 'kb-input';
        if (!id) return;
        try {
            const res = await fetch(`https://kahoot.it/rest/kahoots/${id}`);
            if (!res.ok) throw 1;
            const data = await res.json();
            state.questions = parseQuestions(data.questions);
            state.numQuestions = state.questions.length;
            inputBox.classList.add('ok');
            questionsLabel.textContent = `Question 1 / ${state.numQuestions}`;
        } catch(e) {
            inputBox.classList.add('err');
        }
    };
 
    minimizeButton.onclick = () => {
        const isMin = body.style.display === 'none';
        body.style.display = isMin ? 'flex' : 'none';
        minimizeButton.textContent = isMin ? '-' : '+';
    };
 
    closeButton.onclick = () => uiElement.remove();
 
    // Draggable Logic
    let dragging = false, offset = [0,0];
    handle.addEventListener('mousedown', (e) => {
        if (e.target.tagName === 'BUTTON') return;
        dragging = true;
        const rect = uiElement.getBoundingClientRect();
        offset = [e.clientX - rect.left, e.clientY - rect.top];
    });
    document.addEventListener('mousemove', (e) => {
        if (!dragging) return;
        uiElement.style.left = `${e.clientX - offset[0]}px`;
        uiElement.style.top = `${e.clientY - offset[1]}px`;
    });
    document.addEventListener('mouseup', () => dragging = false);
 
    // Start
    requestAnimationFrame(pollFrame);
 
})();