Apex Learning Quiz Cheat

Highlights Correct Answers in Apex Learning Quiz Menus. Includes Image Support, AI Logic to get the complete right answer, and is 100% Undetected

目前為 2025-11-14 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Apex Learning Quiz Cheat
// @namespace    https://github.com/paysonism
// @version      7.2
// @description  Highlights Correct Answers in Apex Learning Quiz Menus. Includes Image Support, AI Logic to get the complete right answer, and is 100% Undetected
// @author       paysonism
// @match        https://course.apexlearning.com/public/activity/*
// @match        https://*.apexvs.com/public/activity/*
// @grant        GM_xmlhttpRequest
// @connect      generativelanguage.googleapis.com
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // CONFIGURATION - MUST ADD API KEY!
    // ============================================
    const GEMINI_API_KEY = 'YOUR_API_KEY_HERE'; // Get a free key from: https://aistudio.google.com/app/apikey
    const DEBUG_MODE = false; // Set to true for console logging

    const MAX_IMAGE_WIDTH = 800;
    const MAX_IMAGE_HEIGHT = 800;
    const IMAGE_QUALITY = 0.85;
    // ============================================

    let lastQuestionText = '';
    let isProcessing = false;
    let progressInterval = null;

    function log(...args) {
        if (DEBUG_MODE) {
            console.log(...args);
        }
    }

    function logError(...args) {
        if (DEBUG_MODE) {
            console.error(...args);
        }
    }

    function hasQuizContent() {
        const hasStem = document.querySelector('.sia-question-stem') !== null;
        const hasQuestion = document.querySelector('kp-sia-question') !== null;
        const hasDistractors = document.querySelectorAll('.sia-distractor').length > 0;
        return hasStem || hasQuestion || hasDistractors;
    }

    function extractQuestion() {
        const siaQuestion = document.querySelector('kp-sia-question');
        if (siaQuestion) {
            const kpContent = siaQuestion.querySelector('kp-content');
            if (kpContent) {
                const generated = kpContent.querySelector('[class*="kp-generated"]');
                if (generated) {
                    return generated.textContent.trim();
                }
                const text = kpContent.textContent.trim();
                if (text) return text;
            }
        }

        const questionStem = document.querySelector('.sia-question-stem');
        if (questionStem) {
            const allElements = questionStem.querySelectorAll('*');
            for (let el of allElements) {
                if (el.className && el.className.includes('kp-generated')) {
                    const text = el.textContent.trim();
                    if (text) return text;
                }
            }
            const text = questionStem.textContent.trim();
            if (text) return text;
        }

        return null;
    }

    function resizeAndCompressImage(img) {
        return new Promise((resolve, reject) => {
            try {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');

                let width = img.naturalWidth || img.width;
                let height = img.naturalHeight || img.height;

                log(`Original image size: ${width}x${height}`);

                if (width > MAX_IMAGE_WIDTH || height > MAX_IMAGE_HEIGHT) {
                    const ratio = Math.min(MAX_IMAGE_WIDTH / width, MAX_IMAGE_HEIGHT / height);
                    width = Math.floor(width * ratio);
                    height = Math.floor(height * ratio);
                }

                log(`Resized to: ${width}x${height}`);

                canvas.width = width;
                canvas.height = height;
                ctx.drawImage(img, 0, 0, width, height);

                canvas.toBlob((blob) => {
                    if (!blob) {
                        reject(new Error('Failed to create blob'));
                        return;
                    }

                    const reader = new FileReader();
                    reader.onloadend = function() {
                        const base64data = reader.result.split(',')[1];
                        const originalSize = (base64data.length * 0.75 / 1024).toFixed(2);
                        log(`Compressed image size: ${originalSize} KB`);

                        resolve({
                            data: base64data,
                            mimeType: 'image/jpeg'
                        });
                    };
                    reader.onerror = () => reject(new Error('Failed to read blob'));
                    reader.readAsDataURL(blob);
                }, 'image/jpeg', IMAGE_QUALITY);

            } catch (e) {
                reject(e);
            }
        });
    }

    async function extractImages() {
        const images = [];
        const questionArea = document.querySelector('kp-sia-question');
        if (!questionArea) return images;

        const imgElements = questionArea.querySelectorAll('img');
        log(`Found ${imgElements.length} images in question`);

        for (let imgEl of imgElements) {
            try {
                const img = new Image();
                img.crossOrigin = 'anonymous';

                const imageLoadPromise = new Promise((resolve, reject) => {
                    img.onload = () => resolve(img);
                    img.onerror = () => reject(new Error('Failed to load image'));

                    if (imgEl.src) {
                        img.src = imgEl.src;
                    } else if (imgEl.dataset.src) {
                        img.src = imgEl.dataset.src;
                    } else {
                        reject(new Error('No image source found'));
                    }
                });

                await imageLoadPromise;
                const compressedImage = await resizeAndCompressImage(img);
                images.push(compressedImage);
                log('Image processed and compressed');

            } catch (error) {
                logError('Error processing image:', error);

                try {
                    if (imgEl.src && imgEl.src.startsWith('data:')) {
                        const base64Data = imgEl.src.split(',')[1];
                        const mimeType = imgEl.src.split(';')[0].split(':')[1];
                        images.push({
                            data: base64Data,
                            mimeType: mimeType
                        });
                        log('Used original base64 image (compression failed)');
                    }
                } catch (fallbackError) {
                    logError('Fallback also failed:', fallbackError);
                }
            }
        }

        return images;
    }

    function extractAnswers() {
        const answers = [];
        const distractors = document.querySelectorAll('.sia-distractor');

        distractors.forEach((distractor, index) => {
            const choiceLetter = distractor.querySelector('.sia-choice-letter')?.textContent.trim();
            let answerText = null;

            const kpContent = distractor.querySelector('kp-content');
            if (kpContent) {
                const generated = kpContent.querySelector('[class*="kp-generated"]');
                if (generated) {
                    answerText = generated.textContent.trim();
                } else {
                    answerText = kpContent.textContent.trim();
                }
            }

            if (!answerText) {
                const labelDiv = distractor.querySelector('.label');
                if (labelDiv) {
                    answerText = labelDiv.textContent.replace(choiceLetter || '', '').trim();
                }
            }

            if (answerText) {
                answers.push({
                    letter: choiceLetter || String.fromCharCode(65 + index) + '.',
                    text: answerText,
                    element: distractor
                });
            }
        });

        return answers;
    }

    function createProgressBar() {
        let progressBar = document.getElementById('gemini-progress-bar');

        if (!progressBar) {
            progressBar = document.createElement('div');
            progressBar.id = 'gemini-progress-bar';
            progressBar.style.cssText = `
                position: fixed;
                bottom: 25px;
                right: 25px;
                width: 200px;
                height: 6px;
                background: #e0e0e0;
                border-radius: 2px;
                overflow: hidden;
                z-index: 999999;
                opacity: 0;
                transition: opacity 0.3s ease;
            `;

            const progressFill = document.createElement('div');
            progressFill.id = 'gemini-progress-fill';
            progressFill.style.cssText = `
                height: 100%;
                width: 0%;
                background: linear-gradient(90deg, #4285f4, #34a853);
                border-radius: 2px;
                transition: width 0.1s linear;
            `;

            progressBar.appendChild(progressFill);
            document.body.appendChild(progressBar);
        }

        return progressBar;
    }

    function showProgress() {
        const progressBar = createProgressBar();
        const progressFill = document.getElementById('gemini-progress-fill');

        progressBar.style.opacity = '0.75';
        progressFill.style.width = '0%';

        let progress = 0;
        const duration = 3000;
        const interval = 50;
        const increment = (interval / duration) * 100;

        if (progressInterval) {
            clearInterval(progressInterval);
        }

        progressInterval = setInterval(() => {
            progress += increment;
            if (progress >= 95) {
                progress = 95;
            }
            progressFill.style.width = progress + '%';
        }, interval);
    }

    function hideProgress(success = true) {
        if (progressInterval) {
            clearInterval(progressInterval);
            progressInterval = null;
        }

        const progressBar = document.getElementById('gemini-progress-bar');
        const progressFill = document.getElementById('gemini-progress-fill');

        if (progressBar && progressFill) {
            if (success) {
                progressFill.style.width = '100%';
                setTimeout(() => {
                    progressBar.style.opacity = '0';
                    setTimeout(() => {
                        progressFill.style.width = '0%';
                    }, 300);
                }, 300);
            } else {
                progressFill.style.background = '#ea4335';
                setTimeout(() => {
                    progressBar.style.opacity = '0';
                    setTimeout(() => {
                        progressFill.style.width = '0%';
                        progressFill.style.background = 'linear-gradient(90deg, #4285f4, #34a853)';
                    }, 300);
                }, 500);
            }
        }
    }

    function queryGemini(question, answers, images) {
        return new Promise((resolve, reject) => {
            if (!GEMINI_API_KEY || GEMINI_API_KEY === 'YOUR_API_KEY_HERE') {
                reject(new Error('No API key configured'));
                return;
            }

            const answerList = answers.map(a => `${a.letter} ${a.text}`).join('\n');

            let prompt = `You are a knowledgeable assistant operating at a high school level. `;

            if (images.length > 0) {
                prompt += `Analyze the question carefully INCLUDING the provided image(s). The images may contain diagrams, charts, molecular structures, or other visual information needed to answer correctly. `;
            }

            prompt += `Provide ONLY the letter (A, B, C, or D) of the correct answer. Do not include any explanation or additional text - just the single letter.

Question: ${question}

Answer Options:
${answerList}

Think through the question logically and select the most accurate answer based on high school official correct answer trends. This is for a high school level quiz so use appropriate reasoning for that level.

Correct answer letter (A, B, C, or D only):`;

            const parts = [];

            if (images.length > 0) {
                log(`Including ${images.length} optimized image(s) in request`);
                images.forEach((img) => {
                    parts.push({
                        inline_data: {
                            mime_type: img.mimeType,
                            data: img.data
                        }
                    });
                });
            }

            parts.push({ text: prompt });

            const requestData = {
                contents: [{
                    parts: parts
                }]
            };

            const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key=${GEMINI_API_KEY}`;

            log('Querying Gemini' + (images.length > 0 ? ' with vision' : ''));

            GM_xmlhttpRequest({
                method: 'POST',
                url: url,
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify(requestData),
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);

                        if (data.error) {
                            logError('API Error:', data.error);
                            reject(new Error(data.error.message));
                            return;
                        }

                        if (data.candidates && data.candidates.length > 0) {
                            const candidate = data.candidates[0];
                            let answer = null;

                            if (candidate.content && candidate.content.parts) {
                                if (Array.isArray(candidate.content.parts) && candidate.content.parts.length > 0) {
                                    const part = candidate.content.parts[0];
                                    if (part && part.text) {
                                        answer = part.text;
                                    }
                                }
                            }

                            if (answer) {
                                log('Full response text:', answer);

                                const letterMatch = answer.match(/\b[A-D]\b/i);
                                if (letterMatch) {
                                    const finalAnswer = letterMatch[0].toUpperCase();
                                    log('Extracted answer:', finalAnswer);
                                    resolve(finalAnswer);
                                } else {
                                    const anyLetter = answer.match(/[A-D]/i);
                                    if (anyLetter) {
                                        const finalAnswer = anyLetter[0].toUpperCase();
                                        log('Using first A-D found:', finalAnswer);
                                        resolve(finalAnswer);
                                    } else {
                                        reject(new Error('No A-D letter in response'));
                                    }
                                }
                            } else {
                                logError('No text in response');

                                if (candidate.finishReason === 'SAFETY') {
                                    reject(new Error('Response blocked by safety filter'));
                                } else if (candidate.finishReason === 'MAX_TOKENS') {
                                    reject(new Error('MAX_TOKENS issue'));
                                } else {
                                    reject(new Error(`No text (finish: ${candidate.finishReason})`));
                                }
                            }
                        } else {
                            reject(new Error('No candidates'));
                        }
                    } catch (e) {
                        logError('Parse error:', e);
                        reject(e);
                    }
                },
                onerror: function(error) {
                    logError('Request error:', error);
                    reject(error);
                }
            });
        });
    }

    function removeHighlights() {
        document.querySelectorAll('.sia-distractor').forEach(el => {
            const labelDiv = el.querySelector('.label');
            if (labelDiv) {
                labelDiv.style.color = '';
                labelDiv.style.fontWeight = '';
            }
        });
    }

    function highlightAnswer(answers, correctLetter) {
        const normalizedCorrect = correctLetter.replace(/[^A-D]/gi, '').trim().toUpperCase();
        log('Highlighting answer:', normalizedCorrect);

        answers.forEach(answer => {
            const normalizedAnswerLetter = answer.letter.replace(/[^A-D]/gi, '').trim().toUpperCase();

            if (normalizedAnswerLetter === normalizedCorrect) {
                const labelDiv = answer.element.querySelector('.label');
                if (labelDiv) {
                    labelDiv.style.color = '#1e7e34';
                    labelDiv.style.fontWeight = '600';
                }

                log(`Highlighted answer ${answer.letter}`);
            }
        });
    }

    async function processQuiz() {
        if (isProcessing) return;

        if (!hasQuizContent()) {
            return;
        }

        const question = extractQuestion();
        if (!question) {
            return;
        }

        if (question === lastQuestionText) {
            return;
        }

        log('New question detected');
        lastQuestionText = question;
        isProcessing = true;

        removeHighlights();
        showProgress();

        try {
            const answers = extractAnswers();
            if (answers.length === 0) {
                log('No answers found');
                hideProgress(false);
                isProcessing = false;
                return;
            }

            const images = await extractImages();
            if (images.length > 0) {
                log(`Processed ${images.length} optimized image(s)`);
            }

            const correctAnswer = await queryGemini(question, answers, images);
            highlightAnswer(answers, correctAnswer);
            hideProgress(true);

        } catch (error) {
            logError('Error:', error.message);
            hideProgress(false);
        } finally {
            isProcessing = false;
        }
    }

    function startMonitoring() {
        if (!GEMINI_API_KEY || GEMINI_API_KEY === 'YOUR_API_KEY_HERE') {
            console.error('Apex Quiz Auto-Answer: No API key configured. Get one from https://aistudio.google.com/app/apikey');
            return;
        }

        log('Apex Quiz Auto-Answer Active');
        log('Images will be resized to max 800x800');

        setTimeout(processQuiz, 2000);

        const observer = new MutationObserver((mutations) => {
            const relevantChange = mutations.some(mutation => {
                return Array.from(mutation.addedNodes).some(node => {
                    if (node.nodeType === 1) {
                        return node.classList?.contains('sia-distractor') ||
                               node.classList?.contains('sia-question-stem') ||
                               node.querySelector?.('.sia-distractor') ||
                               node.querySelector?.('.sia-question-stem');
                    }
                    return false;
                });
            });

            if (relevantChange) {
                setTimeout(processQuiz, 500);
            }
        });

        const mainContent = document.querySelector('kp-main, main, .sia-content');
        if (mainContent) {
            observer.observe(mainContent, {
                childList: true,
                subtree: true
            });
        }

        setInterval(processQuiz, 3000);
    }

    function init() {
        startMonitoring();
    }

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

    // manual trigger: Ctrl+Shift+R
    document.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.shiftKey && e.key === 'R') {
            e.preventDefault();
            lastQuestionText = '';
            isProcessing = false;
            removeHighlights();
            processQuiz();
        }
    });

})();