commit 07b8fd5c7aad76e864b7965a944e071413cd31ea Author: elias Date: Wed Sep 17 11:56:41 2025 +0200 Add script.js diff --git a/script.js b/script.js new file mode 100644 index 0000000..883334a --- /dev/null +++ b/script.js @@ -0,0 +1,162 @@ +// ==UserScript== +// @name mathtrainer.ai auto solve +// @namespace http://tampermonkey.net/ +// @version 1 +// @description :p +// @author elias +// @match https://mathtrainer.ai/* +// @grant none +// ==/UserScript== + +// made by elias @ 15 sep. 2025 + +(function() { + 'use strict'; + + let lastExpression = ''; + let isProcessing = false; + let InputEvents = {}; + + function hook(element) { + element.addEventListener = new Proxy(element.addEventListener, { + apply(target, _this, args) { + const [type, listener] = args; + if (_this.tagName === 'SECTION' && _this.classList.contains('answer')) { + InputEvents[type] = listener; + } + return target.apply(_this, args); + } + }); + } + hook(HTMLElement.prototype); + + function keypress(target, key) { + const event = new KeyboardEvent('keydown', { + key: key, + code: key.length === 1 ? `Digit${key}` : (key === 'Enter' ? 'Enter' : 'Unknown'), + bubbles: true, + cancelable: true + }); + + if (InputEvents.keydown) { + InputEvents.keydown({ + target: target, + key: key, + type: 'keydown', + preventDefault: () => {}, + stopPropagation: () => {} + }); + } + + target.dispatchEvent(event); + } + + function evaluateMath(expression) { + if (!expression) return null; + expression = expression + .replace(/\\times/g, '*') + .replace(/×/g, '*') + .replace(/÷/g, '/') + .replace(/−/g, '-') + .replace(/\\div/g, '/') + .replace(/\\sqrt\{(\d+)\}/g, 'Math.sqrt($1)') + .replace(/\\sqrt\[(\d+)\]\{(\d+)\}/g, 'Math.pow($2, 1/$1)') + .replace(/\^/g, '**') + .replace(/\s+/g, ''); + try { + const result = Function('"use strict"; return (' + expression + ')')(); + if (Number.isFinite(result)) { + if (Math.abs(result - Math.round(result)) < 1e-10) { + return Math.round(result); // round near-integers (39.99999.. -> 40) + } + return result; + } + return null; + } catch (e) { + return null; + } + } + + function randomDelay(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + async function inputAnswer(answerSection, result) { + const resultStr = String(result); + + await new Promise(resolve => setTimeout(resolve, randomDelay(300, 500))); + + for (let i = 0; i < resultStr.length; i++) { + keypress(answerSection, resultStr[i]); + + if (i < resultStr.length - 1) { + await new Promise(resolve => setTimeout(resolve, randomDelay(300, 500))); + } + } + + await new Promise(resolve => setTimeout(resolve, randomDelay(300, 500))); + } + + async function processQuestion() { + if (isProcessing) return; + isProcessing = true; + + try { + const annotation = document.querySelector('annotation[encoding="application/x-tex"]'); + if (!annotation) { + isProcessing = false; + return; + } + + const expression = annotation.textContent.trim(); + if (!expression || expression === lastExpression) { + isProcessing = false; + return; + } + + lastExpression = expression; + + const result = evaluateMath(expression); + if (result === null) { + isProcessing = false; + return; + } + + // console.log("Detected:", expression); + // console.log("Answer:", result); + + const answerSection = document.querySelector('section.answer'); + if (!answerSection) { + isProcessing = false; + return; + } + + await inputAnswer(answerSection, result); + + // keypress(answerSection, 'Enter'); + // not needed as it gets auto-entered + isProcessing = false; + + } catch (e) { + isProcessing = false; + } + } + + const observer = new MutationObserver(() => { + processQuestion(); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true + }); + + // sometimes observer doesnt catch it :( + // use interval loop as fallback + setInterval(() => { + if (!isProcessing) { + processQuestion(); + } + }, 500); +})(); \ No newline at end of file