CAPTCHA Auto Solver - Multi-Type with Random Delay

CAPTCHA solver for both capstart.php and captcha.php with random delays

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         CAPTCHA Auto Solver - Multi-Type with Random Delay
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  CAPTCHA solver for both capstart.php and captcha.php with random delays
// @author       You
// @match        *://rads.com/*
// @match        *://*/ptc.php*
// @match        *://*.rads.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      huijio-zeracap2.hf.space
// @connect      zerads.com
// ==/UserScript==

(function() {
    'use strict';

    const API_URL = 'https://huijio-zeracap2.hf.space/api/predict';
    let isSolverEnabled = GM_getValue('solverEnabled', true);

    // ==================== RANDOM DELAY FUNCTION ====================
    function getRandomDelay() {
        // Random delay between 1 to 3 minutes (60,000ms to 180,000ms)
        return Math.floor(Math.random() * 120000) + 60000;
    }

    // ==================== TOGGLE UI ====================
    function createToggleUI() {
        const existingToggle = document.getElementById('captcha-solver-toggle');
        if (existingToggle) existingToggle.remove();

        const toggle = document.createElement('div');
        toggle.id = 'captcha-solver-toggle';
        toggle.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            z-index: 10000;
            font-family: Arial, sans-serif;
        `;

        const button = document.createElement('button');
        button.innerHTML = isSolverEnabled ? '🔴 STOP Solver' : '🟢 START Solver';
        button.style.cssText = `
            background: ${isSolverEnabled ? '#ff4444' : '#4CAF50'};
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
        `;

        button.onclick = function() {
            isSolverEnabled = !isSolverEnabled;
            GM_setValue('solverEnabled', isSolverEnabled);
            button.innerHTML = isSolverEnabled ? '🔴 STOP Solver' : '🟢 START Solver';
            button.style.background = isSolverEnabled ? '#ff4444' : '#4CAF50';

            const status = document.getElementById('solver-status');
            if (status) {
                status.textContent = isSolverEnabled ? 'Status: ACTIVE' : 'Status: STOPPED';
                status.style.color = isSolverEnabled ? '#4CAF50' : '#ff4444';
            }

            console.log(`CAPTCHA Solver ${isSolverEnabled ? 'ENABLED' : 'DISABLED'}`);

            if (isSolverEnabled) {
                if (isCaptchaPage()) {
                    setTimeout(solveCaptcha, 2000);
                }
            }
        };

        const status = document.createElement('div');
        status.id = 'solver-status';
        status.textContent = `Status: ${isSolverEnabled ? 'ACTIVE' : 'STOPPED'}`;
        status.style.cssText = `
            color: ${isSolverEnabled ? '#4CAF50' : '#ff4444'};
            font-size: 12px;
            text-align: center;
            margin-top: 5px;
            font-weight: bold;
        `;

        toggle.appendChild(button);
        toggle.appendChild(status);
        document.body.appendChild(toggle);

        return toggle;
    }

    // ==================== PAGE DETECTION ====================
    function isCaptchaPage() {
        return (document.querySelector('img[src*="captcha.php"]') ||
                document.querySelector('img[src*="capstart.php"]')) &&
               document.querySelector('a[href*="ptc.php"]');
    }

    function getCaptchaType() {
        if (document.querySelector('img[src*="capstart.php"]')) {
            return 'capstart';
        } else if (document.querySelector('img[src*="captcha.php"]')) {
            return 'normal';
        }
        return null;
    }

    function hasMostRelativeText() {
        const bodyText = document.body.innerText;
        return bodyText.includes('Please Click Most Relative');
    }

    // ==================== SMART REFRESH CHECK ====================
    function shouldRefreshPage() {
        const questionImg = document.querySelector('img[src*="captcha.php"], img[src*="capstart.php"]');
        const answersLoaded = document.querySelectorAll('a[href*="ptc.php"]').length > 0;

        if (answersLoaded && questionImg) {
            return questionImg.naturalWidth === 0 || !questionImg.complete;
        }

        return false;
    }

    // ==================== IMAGE PROCESSING ====================
    function imageToBase64(img) {
        return new Promise((resolve) => {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = img.naturalWidth;
            canvas.height = img.naturalHeight;
            ctx.drawImage(img, 0, 0);

            try {
                const base64 = canvas.toDataURL('image/jpeg', 0.9);
                resolve(base64);
            } catch (e) {
                const base64 = canvas.toDataURL('image/png');
                resolve(base64);
            }
        });
    }

    function extractCaptchaIdFromUrl(url) {
        // Extract cid or scid parameter from URL
        const cidMatch = url.match(/cid=(\d+)/);
        const scidMatch = url.match(/scid=(\d+)/);
        return cidMatch ? cidMatch[1] : (scidMatch ? scidMatch[1] : null);
    }

    function extractImageData() {
        console.log('🔍 Extracting CAPTCHA data...');

        const questionImg = document.querySelector('img[src*="captcha.php"], img[src*="capstart.php"]');
        if (!questionImg) {
            console.log('❌ No question image found');
            return null;
        }

        const answerLinks = Array.from(document.querySelectorAll('a[href*="ptc.php"]'));
        const answerData = [];

        for (const link of answerLinks) {
            const captchaId = extractCaptchaIdFromUrl(link.href);
            const img = link.querySelector('img');

            if (captchaId && img) {
                answerData.push({
                    captcha_id: captchaId,
                    element: link,
                    image: img
                });
            }
        }

        if (answerData.length === 0) {
            console.log('❌ No answer images found');
            return null;
        }

        const captchaType = getCaptchaType();
        console.log(`✅ Found ${answerData.length} answers for ${captchaType} CAPTCHA with IDs:`, answerData.map(a => a.captcha_id));

        return {
            question: questionImg,
            answers: answerData,
            type: captchaType
        };
    }

    async function extractImagesAsBase64() {
        const captchaData = extractImageData();
        if (!captchaData) return null;

        // Wait for images to load
        await new Promise(resolve => setTimeout(resolve, 1500));

        // Check if question image failed to load but answers are loaded
        if (shouldRefreshPage()) {
            console.log('🔄 Question image failed but answers loaded - refreshing...');
            location.reload();
            return null;
        }

        // Convert question to base64
        const questionBase64 = await imageToBase64(captchaData.question);

        // Convert answers to base64
        const answersWithData = [];
        for (const answer of captchaData.answers) {
            if (!answer.image.complete) {
                await new Promise(resolve => {
                    answer.image.onload = resolve;
                    answer.image.onerror = resolve;
                    setTimeout(resolve, 2000);
                });
            }

            const imageBase64 = await imageToBase64(answer.image);
            answersWithData.push({
                captcha_id: answer.captcha_id,
                image_base64: imageBase64,
                element: answer.element
            });
        }

        console.log(`✅ All images converted to base64 for ${captchaData.type} CAPTCHA`);
        return {
            question: questionBase64,
            answers: answersWithData,
            elements: captchaData.answers.map(a => a.element),
            type: captchaData.type
        };
    }

    // ==================== PROGRESS BAR WAIT ====================
    function waitForProgressBar() {
        return new Promise((resolve) => {
            let attempts = 0;
            const maxAttempts = 69;

            const checkProgress = () => {
                if (!isSolverEnabled) {
                    console.log('⏸️ Solver disabled during progress wait');
                    return;
                }

                attempts++;
                const progressBar = document.getElementById('myProgress');
                const captchaDiv = document.getElementById('captcha');

                // For capstart pages, there's no progress bar, just wait a bit
                if (getCaptchaType() === 'capstart') {
                    console.log('⏳ Capstart page - waiting 3 seconds...');
                    setTimeout(() => resolve(true), 3000);
                    return;
                }

                if ((!progressBar || progressBar.style.display === 'none') &&
                    captchaDiv && captchaDiv.style.display !== 'none') {
                    console.log('✅ Progress bar complete');
                    resolve(true);
                } else if (attempts >= maxAttempts) {
                    console.log('❌ Progress bar timeout');
                    resolve(false);
                } else {
                    setTimeout(checkProgress, 1000);
                }
            };
            checkProgress();
        });
    }

    // ==================== API CALL ====================
    function callAPI(questionBase64, answersWithData) {
        return new Promise((resolve, reject) => {
            if (!isSolverEnabled) {
                reject('Solver disabled by user');
                return;
            }

            console.log('📡 Calling API...');

            const requestData = {
                question_base64: questionBase64,
                answers: answersWithData.map(a => ({
                    captcha_id: a.captcha_id,
                    image_base64: a.image_base64
                }))
            };

            GM_xmlhttpRequest({
                method: 'POST',
                url: API_URL,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                data: JSON.stringify(requestData),
                timeout: 60000,
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (response.status === 200 && data.success) {
                            resolve(data);
                        } else {
                            reject(data.error || 'API error');
                        }
                    } catch (e) {
                        reject('Failed to parse API response');
                    }
                },
                onerror: () => reject('API request failed'),
                ontimeout: () => reject('API timeout')
            });
        });
    }

    async function callAPIWithRetry(questionBase64, answersWithData, maxRetries = 20) {
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            if (!isSolverEnabled) break;

            try {
                console.log(`🔄 API Attempt ${attempt}/${maxRetries}`);
                return await callAPI(questionBase64, answersWithData);
            } catch (error) {
                console.log(`❌ Attempt ${attempt} failed:`, error);
                if (attempt < maxRetries) {
                    await new Promise(resolve => setTimeout(resolve, 5000));
                }
            }
        }
        throw new Error('All API attempts failed');
    }

    // ==================== CLICK LOGIC ====================
    function findElementByCaptchaId(captchaId, elements) {
        for (const element of elements) {
            const elementCaptchaId = extractCaptchaIdFromUrl(element.href);
            if (elementCaptchaId === captchaId) {
                return element;
            }
        }
        return null;
    }

    function showVisualFeedback(element, confidence, captchaType) {
        element.style.border = '3px solid #00ff00';
        element.style.boxShadow = '0 0 15px #00ff00';

        const overlay = document.createElement('div');
        overlay.textContent = `${confidence.toFixed(1)}%`;
        overlay.style.cssText = `
            position: absolute;
            background: rgba(0,255,0,0.8);
            color: white;
            padding: 2px 6px;
            border-radius: 3px;
            font-size: 12px;
            font-weight: bold;
            z-index: 1000;
        `;
        element.style.position = 'relative';
        element.appendChild(overlay);

        console.log(`🎯 ${captchaType.toUpperCase()} CAPTCHA - Best match: ${confidence.toFixed(1)}%`);
    }

    // ==================== MAIN SOLVER ====================
    async function solveCaptcha() {
        // Check if solver is disabled
        if (!isSolverEnabled) {
            console.log('⏸️ Solver disabled - skipping');
            return;
        }

        // Check for "Most Relative" text and stop if found
        if (hasMostRelativeText()) {
            console.log('🛑 "Please solve captcha to surf PTC Ads" or similar text detected - stopping solver');
            isSolverEnabled = false;
            GM_setValue('solverEnabled', false);

            // Update UI
            const button = document.querySelector('#captcha-solver-toggle button');
            const status = document.getElementById('solver-status');
            if (button) {
                button.innerHTML = '🟢 START Solver';
                button.style.background = '#4CAF50';
            }
            if (status) {
                status.textContent = 'Status: STOPPED (Manual CAPTCHA)';
                status.style.color = '#ff4444';
            }
            return;
        }

        try {
            console.log('🔍 Starting CAPTCHA solving...');

            const captchaType = getCaptchaType();
            console.log(`📝 Detected CAPTCHA type: ${captchaType}`);

            // Apply random delay only for capstart pages
            if (captchaType === 'capstart') {
                const delay = getRandomDelay();
                console.log(`⏰ Capstart page - applying random delay: ${Math.round(delay/1000)} seconds`);
                await new Promise(resolve => setTimeout(resolve, delay));
                if (!isSolverEnabled) return;
            }

            // Wait for progress bar (only for normal captcha)
            const progressComplete = await waitForProgressBar();
            if (!progressComplete || !isSolverEnabled) {
                console.log('⏸️ Solver stopped during progress wait');
                return;
            }

            // Wait a bit more for images
            await new Promise(resolve => setTimeout(resolve, 2000));
            if (!isSolverEnabled) return;

            // Extract images
            const imagesData = await extractImagesAsBase64();
            if (!imagesData || !isSolverEnabled) return;

            console.log(`📸 Processing ${imagesData.answers.length} answers for ${imagesData.type} CAPTCHA`);

            // Call API
            const response = await callAPIWithRetry(imagesData.question, imagesData.answers);
            if (!response || !isSolverEnabled) return;

            // Find and click best match
            const bestElement = findElementByCaptchaId(response.best_match, imagesData.elements);
            if (bestElement && isSolverEnabled) {
                console.log(`🎯 Clicking ${imagesData.type} CAPTCHA - ID: ${response.best_match} (${response.best_confidence.toFixed(1)}%)`);

                showVisualFeedback(bestElement, response.best_confidence, imagesData.type);

                setTimeout(() => {
                    if (isSolverEnabled) {
                        bestElement.click();
                    }
                }, 1500);
            }

        } catch (error) {
            console.log('❌ Solver error:', error);
        }
    }

    // ==================== INITIALIZATION ====================
    function init() {
        console.log('🚀 CAPTCHA Solver initialized - Multi-Type with Random Delay');

        // Create toggle UI
        createToggleUI();

        // Only auto-start if enabled and on CAPTCHA page
        if (isSolverEnabled && isCaptchaPage()) {
            const captchaType = getCaptchaType();
            console.log(`🎯 ${captchaType.toUpperCase()} CAPTCHA page detected - starting solver`);

            // Start immediately for normal captcha, delayed for capstart
            if (captchaType === 'capstart') {
                const initialDelay = getRandomDelay();
                console.log(`⏰ Initial random delay for capstart: ${Math.round(initialDelay/1000)} seconds`);
                setTimeout(solveCaptcha, initialDelay);
            } else {
                setTimeout(solveCaptcha, 3000);
            }
        }
    }

    // ==================== START SCRIPT ====================
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();