Greasy Fork

Stealth Canvas Answer Highlighter V7.0

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

// ==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.');
        }
    });
})();