Universal Math CAPTCHA Solver

Solves ALL basic math CAPTCHAs (+, -, *, /) in any format

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Universal Math CAPTCHA Solver
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Solves ALL basic math CAPTCHAs (+, -, *, /) in any format
// @author       ChrisN40
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const config = {
        checkInterval: 1000,
        maxAttempts: 3,
        retryDelay: 1000,
        mathPatterns: [
            // Standard equation formats
            /(?:solve|calculate|what is|answer)\s*(?:the )?(?:following )?(?:math )?(?:problem|function|question)?[:]?\s*([\d+\-*\/=. ]+)/i,
            /([\d]+\s*[\+\-\*\/]\s*[\d]+)/,

            // Word problem formats
            /first number is (\d+)[^\d]+(\d+)[^\d]+([\+\-\*\/])/i,
            /sum of (\d+) and (\d+)/i,
            /add (\d+) (?:and|to) (\d+)/i,
            /subtract (\d+) (?:from|and) (\d+)/i,
            /product of (\d+) and (\d+)/i,
            /multiply (\d+) (?:and|by) (\d+)/i,
            /divide (\d+) (?:by|and) (\d+)/i,
            /(\d+) plus (\d+)/i,
            /(\d+) minus (\d+)/i,
            /(\d+) times (\d+)/i,
            /(\d+) multiplied by (\d+)/i,
            /(\d+) divided by (\d+)/i,

            // Special cases
            /how much is (\d+) ([\+\-\*\/]) (\d+)/i
        ]
    };

    class UniversalMathSolver {
        constructor() {
            this.attempts = new WeakMap();
            this.init();
        }

        init() {
            console.log('Universal Math Solver initialized');
            this.setupMutationObserver();
            this.checkMathCaptchas();
            setInterval(() => this.checkMathCaptchas(), config.checkInterval);
        }

        setupMutationObserver() {
            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === 1 && this.isMathCaptcha(node.textContent)) {
                            this.processMathElement(node);
                        }
                    });
                });
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true,
                characterData: true
            });
        }

        checkMathCaptchas() {
            const textElements = document.querySelectorAll('div, p, span, label, td, li, b, strong, h1, h2, h3, h4, h5, h6');

            textElements.forEach(element => {
                if (this.shouldProcessElement(element)) {
                    this.processMathElement(element);
                }
            });
        }

        shouldProcessElement(element) {
            if (!this.attempts.has(element)) {
                this.attempts.set(element, 0);
            }
            return this.attempts.get(element) < config.maxAttempts &&
                   this.isMathCaptcha(element.textContent);
        }

        isMathCaptcha(text) {
            if (!text) return false;
            return config.mathPatterns.some(pattern => pattern.test(text));
        }

        processMathElement(element) {
            const attempts = this.attempts.get(element);
            this.attempts.set(element, attempts + 1);

            try {
                const mathProblem = this.extractMathProblem(element.textContent);
                if (mathProblem) {
                    const solution = this.solveMathProblem(mathProblem);
                    if (solution !== null) {
                        this.fillSolution(solution, element);
                    }
                }
            } catch (error) {
                console.error('Math CAPTCHA solve error:', error);
                if (attempts < config.maxAttempts - 1) {
                    setTimeout(() => this.processMathElement(element), config.retryDelay);
                }
            }
        }

        extractMathProblem(text) {
            for (const pattern of config.mathPatterns) {
                const match = text.match(pattern);
                if (match) {
                    // Handle word problems
                    if (match[1] && match[2] && match[3]) {
                        const num1 = parseFloat(match[1]);
                        const num2 = parseFloat(match[2]);
                        const operator = this.normalizeOperator(match[3]);

                        // Handle subtraction word order ("subtract A from B" = B - A)
                        if (match[0].includes('subtract') && operator === '-') {
                            return `${num2}${operator}${num1}`;
                        }
                        return `${num1}${operator}${num2}`;
                    }
                    // Handle standard equations
                    else if (match[1]) {
                        return match[1].replace(/\s/g, '');
                    }
                }
            }
            return null;
        }

        normalizeOperator(op) {
            const opMap = {
                'plus': '+',
                'add': '+',
                'sum': '+',
                'minus': '-',
                'subtract': '-',
                'times': '*',
                'multiplied': '*',
                'product': '*',
                'divide': '/',
                'divided': '/'
            };
            return opMap[op.toLowerCase()] || op;
        }

        solveMathProblem(problem) {
            try {
                // Clean and validate the expression
                const cleanExpr = problem.replace(/[^\d+\-*\/.]/g, '');
                if (!/^[\d+\-*\/.]+$/.test(cleanExpr)) return null;

                // Split into tokens (numbers and operators)
                const tokens = cleanExpr.split(/([\+\-\*\/])/).filter(x => x);

                // Convert to numbers and operators
                const elements = [];
                for (const token of tokens) {
                    if (['+', '-', '*', '/'].includes(token)) {
                        elements.push({ type: 'operator', value: token });
                    } else {
                        elements.push({ type: 'number', value: parseFloat(token) });
                    }
                }

                // First pass: multiplication and division
                for (let i = 0; i < elements.length; i++) {
                    const el = elements[i];
                    if (el.type === 'operator' && (el.value === '*' || el.value === '/')) {
                        const left = elements[i-1].value;
                        const right = elements[i+1].value;
                        let result;

                        if (el.value === '*') {
                            result = left * right;
                        } else {
                            if (right === 0) return '∞'; // Division by zero
                            result = left / right;
                        }

                        // Replace the three elements (left, op, right) with result
                        elements.splice(i-1, 3, { type: 'number', value: result });
                        i -= 2; // Adjust index after replacement
                    }
                }

                // Second pass: addition and subtraction
                let result = elements[0].value;
                for (let i = 1; i < elements.length; i += 2) {
                    const operator = elements[i].value;
                    const number = elements[i+1].value;

                    if (operator === '+') {
                        result += number;
                    } else {
                        result -= number;
                    }
                }

                // Round to 2 decimal places if needed
                return result % 1 === 0 ? result : parseFloat(result.toFixed(2));
            } catch (e) {
                console.error('Math solving failed:', e);
                return null;
            }
        }

        fillSolution(solution, referenceElement) {
            const input = this.findInputField(referenceElement);
            if (!input) return false;

            // Try different methods to set the value
            const methods = [
                () => { input.value = solution; return input.value == solution; },
                () => {
                    input.focus();
                    input.value = solution;
                    this.triggerEvents(input);
                    return input.value == solution;
                },
                () => {
                    const setter = Object.getOwnPropertyDescriptor(
                        HTMLInputElement.prototype, 'value').set;
                    setter.call(input, solution);
                    this.triggerEvents(input);
                    return input.value == solution;
                }
            ];

            for (const method of methods) {
                try {
                    if (method()) {
                        console.log(`Solved: ${solution}`);
                        return true;
                    }
                } catch (e) {
                    console.debug('Method failed:', e);
                }
            }
            return false;
        }

        findInputField(referenceElement) {
            const selectors = [
                'input[type="text"]',
                'input[type="number"]',
                'input:not([type])',
                'textarea',
                '#answer',
                '#solution',
                '#captcha',
                '#mathAnswer'
            ];

            // Check in parent containers
            let container = referenceElement;
            while (container) {
                for (const selector of selectors) {
                    const input = container.querySelector(selector);
                    if (input) return input;
                }
                container = container.parentElement;
            }

            // Check nearby elements
            const allInputs = document.querySelectorAll('input, textarea');
            const refRect = referenceElement.getBoundingClientRect();
            let closestInput = null;
            let minDistance = Infinity;

            allInputs.forEach(input => {
                const inputRect = input.getBoundingClientRect();
                const distance = Math.sqrt(
                    Math.pow(inputRect.left - refRect.left, 2) +
                    Math.pow(inputRect.top - refRect.top, 2)
                );
                if (distance < minDistance) {
                    minDistance = distance;
                    closestInput = input;
                }
            });

            return closestInput;
        }

        triggerEvents(input) {
            ['focus', 'keydown', 'keypress', 'keyup', 'input', 'change', 'blur'].forEach(event => {
                input.dispatchEvent(new Event(event, {
                    bubbles: true,
                    cancelable: true
                }));
            });
        }
    }

    // Start the solver
    new UniversalMathSolver();
})();