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