161 lines
4.6 KiB
JavaScript
161 lines
4.6 KiB
JavaScript
// ==UserScript==
|
||
// @name mathtrainer.ai auto solve
|
||
// @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);
|
||
})(); |