Stealth Canvas Answer Highlighter V7.0

Silently highlights correct Canvas quiz answers on hover with improved OCR accuracy and dynamic content handling.

คุณจะต้องติดตั้งส่วนขยาย เช่น 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         Stealth Canvas Answer Highlighter V7.0
// @namespace    http://tampermonkey.net/
// @version      7.0
// @description  Silently highlights correct Canvas quiz answers on hover with improved OCR accuracy and dynamic content handling.
// @author       Blake
// @match        *://*.instructure.com/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    /**
     * The most reliable version of Stealth Canvas Answer Highlighter.
     * Improvements include better OCR accuracy, dynamic content handling, and more robust matching logic.
     */

    // OCR setup
    const OCR_ENGINE = Tesseract.create({
        langPath: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/tesseract.min.js',
        workerPath: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/worker.min.js'
    });

    // Helper function to handle OCR extraction
    async function extractAnswersFromOCR() {
        try {
            // OCR extraction logic: Extract text from the quiz page image (if needed)
            const images = document.querySelectorAll('img'); // Grab all images (including background images if any)
            let textResults = [];

            for (let img of images) {
                if (img.complete && img.naturalHeight > 0) {
                    const result = await OCR_ENGINE.recognize(img);
                    textResults.push(result.text.trim());
                }
            }

            return textResults;
        } catch (error) {
            console.error('OCR extraction failed:', error);
            return [];
        }
    }

    // Helper function to get answers using the API (if possible)
    async function getAnswersFromAPI() {
        try {
            const response = await fetch('https://api.clevergoose.ai/canvas/answers', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    html: document.documentElement.outerHTML,
                    url: window.location.href
                })
            });

            if (!response.ok) return [];

            const data = await response.json();
            return data.answers || [];
        } catch (error) {
            console.error('API request failed:', error);
            return [];
        }
    }

    // Normalize text for comparison
    function normalize(text) {
        return text.trim().toLowerCase().replace(/\s+/g, '');
    }

    // Match the correct answers to the DOM elements
    function matchAnswersToElements(correctAnswers) {
        const answerElements = document.querySelectorAll('label, div, span, input');
        const matchedElements = [];

        correctAnswers.forEach(answer => {
            const normalizedCorrect = normalize(answer);
            answerElements.forEach(el => {
                if (el.innerText && normalize(el.innerText).includes(normalizedCorrect)) {
                    matchedElements.push(el);
                }
            });
        });

        return matchedElements;
    }

    // Apply styles for stealth highlighting
    function applyStealthHighlight(elements) {
        elements.forEach(el => {
            el.addEventListener('mouseenter', () => {
                el.style.outline = '2px solid black';
                el.style.fontWeight = 'bold';
                el.style.textDecoration = 'underline';
            });
            el.addEventListener('mouseleave', () => {
                el.style.outline = '';
                el.style.fontWeight = '';
                el.style.textDecoration = '';
            });
        });
    }

    // Wait for content to load dynamically before applying highlighting
    async function waitForContentToLoad() {
        // Wait up to 5 seconds for the content to load properly before proceeding with highlighting
        const maxWaitTime = 5000;
        const startTime = Date.now();
        
        while (Date.now() - startTime < maxWaitTime) {
            const contentLoaded = document.querySelectorAll('label, div, span').length > 0;
            if (contentLoaded) {
                return true;
            }
            await new Promise(resolve => setTimeout(resolve, 200)); // Check every 200ms
        }
        return false;
    }

    // Core function to initialize stealth highlight
    async function initStealthHighlight() {
        const answersFromAPI = await getAnswersFromAPI();
        const answersFromOCR = await extractAnswersFromOCR();

        // Combine answers from both API and OCR
        const combinedAnswers = [...new Set([...answersFromAPI, ...answersFromOCR])];

        if (!combinedAnswers.length) {
            console.log('No answers detected.');
            return;
        }

        const matchedElements = matchAnswersToElements(combinedAnswers);
        applyStealthHighlight(matchedElements);
    }

    // Trigger after page load with a slight delay for Canvas DOM to be ready
    window.addEventListener('load', async () => {
        const contentLoaded = await waitForContentToLoad();
        if (contentLoaded) {
            setTimeout(initStealthHighlight, 1000); // Delay the function execution for 1 second to ensure DOM is stable
        } else {
            console.error('Timed out waiting for content to load.');
        }
    });
})();