Recaptcha Solver — Debuggable (Draggable Overlay + Test)

Debuggable reCAPTCHA audio solver — overlay, ping/test servers, DOM dump, runs in recaptcha frames

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Recaptcha Solver — Debuggable (Draggable Overlay + Test)
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  Debuggable reCAPTCHA audio solver — overlay, ping/test servers, DOM dump, runs in recaptcha frames
// @author       you
// @match        https://www.google.com/recaptcha/api2/*
// @match        https://www.recaptcha.net/recaptcha/api2/*
// @match        *://*/recaptcha/*
// @run-at       document-idle
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      engageub.pythonanywhere.com
// @connect      engageub1.pythonanywhere.com
// ==/UserScript==

(function () {
  'use strict';

  // CONFIG
  const MAX_ATTEMPTS = 6;
  const serversList = ["https://engageub.pythonanywhere.com","https://engageub1.pythonanywhere.com"];
  let latencyList = Array(serversList.length).fill(99999);
  let requestCount = 0;
  let waitingForAudioResponse = false;
  let audioUrl = "";
  const recaptchaLanguage = document.documentElement?.lang || 'en-US';

  // SELECTOR CANDIDATES (проверяем несколько вариантов)
  const CHECKBOX_CANDS = ['.recaptcha-checkbox-border', '#recaptcha-anchor', '.recaptcha-checkbox-checkmark'];
  const AUDIO_BUTTON_CANDS = ['#recaptcha-audio-button', '.rc-audiochallenge-playing .rc-button', '.rc-audiochallenge-tdownload-link'];
  const AUDIO_SOURCE_CANDS = ['#audio-source', 'audio[src]', 'audio > source', '.rc-audiochallenge-tdownload-link a'];
  const AUDIO_RESPONSE_CANDS = ['#audio-response', '.rc-audiochallenge-response-field', 'input[name="audio-response"]'];
  const RELOAD_BUTTON_CANDS = ['#recaptcha-reload-button', '.rc-audiochallenge-tdreload-link', 'button[aria-label="Reload"]'];
  const VERIFY_BUTTON_CANDS = ['#recaptcha-verify-button', 'button#recaptcha-verify-button', 'button[aria-label="Verify"]'];
  const RECAPTCHA_STATUS = '#recaptcha-accessible-status';
  const DOSCAPTCHA = '.rc-doscaptcha-body';

  // UTILS
  const q = s => document.querySelector(s);
  function findFirst(list) {
    for (const s of list) {
      const el = document.querySelector(s);
      if (el) return el;
    }
    return null;
  }
  function now() { return (new Date()).toLocaleTimeString(); }
  function addConsole(msg) { console.log(`[RecaptchaSolver:${now()}] ${msg}`); }

  // OVERLAY (draggable + buttons)
  const overlayPosDefault = { left: (window.innerWidth - 340) + 'px', top: (window.innerHeight - 200) + 'px' };
  const storedPos = GM_getValue('overlayPos', overlayPosDefault);
  const collapsed = GM_getValue('overlayCollapsed', false);
  const overlay = document.createElement('div');
  overlay.style.cssText = `position:fixed; left:${storedPos.left}; top:${storedPos.top}; width:340px; z-index:2147483647; font:12px monospace;`;
  overlay.id = 'recaptcha-solver-overlay';
  overlay.innerHTML = `
    <div id="rs-header" style="background:#222;color:#fff;padding:6px;cursor:move;display:flex;justify-content:space-between;align-items:center;">
      <div style="font-weight:700">Recaptcha Solver (debug)</div>
      <div>
        <button id="rs-toggle" style="margin-right:6px"> ${collapsed ? '▸' : '▾'} </button>
        <button id="rs-ping" title="Ping servers">Ping</button>
        <button id="rs-test" title="Send current audio to server">Test</button>
        <button id="rs-dump" title="Dump DOM selectors">Dump</button>
      </div>
    </div>
    <div id="rs-body" style="background:#000;color:#0f0;padding:8px;height:180px;overflow:auto;display:${collapsed ? 'none' : 'block'};">
      <div id="rs-log"></div>
    </div>
  `;
  document.body.appendChild(overlay);
  const header = document.getElementById('rs-header');
  const bodyBox = document.getElementById('rs-body');
  const logBox = document.getElementById('rs-log');
  function log(message) {
    const line = document.createElement('div');
    line.innerHTML = `[${now()}] ${message}`;
    logBox.appendChild(line);
    logBox.scrollTop = logBox.scrollHeight;
    addConsole(message);
  }

  // Dragging
  (function makeDraggable() {
    let isDown = false, startX=0, startY=0, startLeft=0, startTop=0;
    header.addEventListener('mousedown', e => {
      isDown = true;
      startX = e.clientX; startY = e.clientY;
      const rect = overlay.getBoundingClientRect();
      startLeft = rect.left; startTop = rect.top;
      e.preventDefault();
    });
    document.addEventListener('mousemove', e => {
      if (!isDown) return;
      const dx = e.clientX - startX, dy = e.clientY - startY;
      overlay.style.left = (startLeft + dx) + 'px';
      overlay.style.top = (startTop + dy) + 'px';
    });
    document.addEventListener('mouseup', () => {
      if (!isDown) return;
      isDown = false;
      GM_setValue('overlayPos', { left: overlay.style.left, top: overlay.style.top });
    });
  })();

  // Toggle collapse
  document.getElementById('rs-toggle').addEventListener('click', () => {
    const btn = document.getElementById('rs-toggle');
    const isHidden = bodyBox.style.display === 'none';
    bodyBox.style.display = isHidden ? 'block' : 'none';
    btn.textContent = isHidden ? '▾' : '▸';
    GM_setValue('overlayCollapsed', !isHidden);
  });

  // Helper: ping server (show responseText and latency)
  async function pingServer(index) {
    const url = serversList[index];
    log(`Pinging ${url} ...`);
    const start = Date.now();
    GM_xmlhttpRequest({
      method: 'GET',
      url: url,
      timeout: 8000,
      onload: function (r) {
        const ms = Date.now() - start;
        latencyList[index] = ms;
        log(`Ping ${url} → status ${r.status}, text: "${(r.responseText||'').slice(0,200)}", latency ${ms}ms`);
      },
      onerror: function (e) { log(`Ping error ${url} → ${String(e)}`); },
      ontimeout: function () { log(`Ping timeout ${url}`); }
    });
  }

  // Button handlers
  document.getElementById('rs-ping').addEventListener('click', () => {
    for (let i=0; i<serversList.length; i++) pingServer(i);
  });

  document.getElementById('rs-dump').addEventListener('click', () => {
    dumpSelectors();
  });

  document.getElementById('rs-test').addEventListener('click', () => {
    const src = discoverAudioSrc();
    if (!src) { log('No audio-src found to test. Open audio challenge first.'); return; }
    log(`Testing servers with audio: ${src}`);
    testServersWithAudio(src);
  });

  // DISCOVERY helpers
  function discoverAudioSrc() {
    // try many variants
    for (const sel of AUDIO_SOURCE_CANDS) {
      const el = document.querySelector(sel);
      if (!el) continue;
      // if <source> inside <audio>
      if (el.tagName && el.tagName.toLowerCase() === 'source' && el.src) return el.src;
      if (el.tagName && el.tagName.toLowerCase() === 'audio' && el.src) return el.src;
      // link
      if (el.href) return el.href;
      // if audio element has nested source
      const nested = el.querySelector && el.querySelector('source');
      if (nested && nested.src) return nested.src;
    }
    // fallback: search any audio element
    const aud = document.querySelector('audio');
    if (aud && aud.src) return aud.src;
    const s = document.querySelector('audio source');
    if (s && s.src) return s.src;
    return null;
  }

  function dumpSelectors() {
    log('=== DUMP SELECTORS START ===');
    log('Current URL: ' + window.location.href);
    log('Document title: ' + document.title);
    for (const s of ['#recaptcha-anchor', '.recaptcha-checkbox-border', '#recaptcha-accessible-status']) {
      log(`${s} → ${document.querySelector(s) ? 'FOUND' : 'missing'}`);
    }
    const src = discoverAudioSrc();
    log('audio-src: ' + (src || 'not found'));
    log('audio-response input present?: ' + (findFirst(AUDIO_RESPONSE_CANDS) ? 'yes' : 'no'));
    log('reload button?: ' + (findFirst(RELOAD_BUTTON_CANDS) ? 'yes' : 'no'));
    log('verify button?: ' + (findFirst(VERIFY_BUTTON_CANDS) ? 'yes' : 'no'));
    log('doscapcha text?: ' + (document.querySelector(DOSCAPTCHA) ? document.querySelector(DOSCAPTCHA).innerText.slice(0,120) : 'no'));
    log('=== DUMP SELECTORS END ===');
  }

  // choose fastest server (or fallback)
  function chooseServer() {
    let idx = 0;
    try {
      idx = latencyList.indexOf(Math.min(...latencyList));
      if (!isFinite(latencyList[idx]) || latencyList[idx] > 90000) idx = 0;
    } catch(e) { idx = 0; }
    return { url: serversList[idx], idx };
  }

  // send audio url to server(s) sequentially until valid response
  function testServersWithAudio(audioSrc) {
    let tried = 0;
    function tryIndex(i) {
      if (i >= serversList.length) { log('All servers tried — no valid response.'); return; }
      const url = serversList[i];
      log(`POST -> ${url} (audio=${audioSrc.slice(0,80)}...)`);
      GM_xmlhttpRequest({
        method: 'POST',
        url: url,
        headers: {"Content-Type":"application/x-www-form-urlencoded"},
        data: `input=${encodeURIComponent(audioSrc)}&lang=${encodeURIComponent(recaptchaLanguage)}`,
        timeout: 60000,
        onload: function(r) {
          log(`Server ${url} responded (status ${r.status}): "${(r.responseText||'').slice(0,300)}"`);
          const t = (r.responseText||'').trim();
          if (t && t !== '0' && !t.includes('<') && t.length >= 1 && t.length <= 200) {
            log(`Using server ${url} result: "${t}" → will attempt to insert into response field.`);
            const inp = findFirst(AUDIO_RESPONSE_CANDS);
            const verify = findFirst(VERIFY_BUTTON_CANDS);
            if (inp) {
              inp.value = t;
              inp.dispatchEvent(new Event('input',{bubbles:true}));
              log('Inserted result into input field.');
              if (verify) { verify.click(); log('Clicked verify.'); }
            } else {
              log('No audio-response input found on page to insert value into.');
            }
          } else {
            log(`Invalid/empty result from ${url}: "${t}" — trying next server...`);
            tryIndex(i+1);
          }
        },
        onerror: function(e){ log(`Request error to ${url}: ${String(e)}`); tryIndex(i+1); },
        ontimeout: function(){ log(`Timeout to ${url}`); tryIndex(i+1); }
      });
    }
    tryIndex(0);
  }

  // main interval: try to click checkbox / open audio / send
  const mainInterval = setInterval(() => {
    try {
      // If recaptcha status shows changed → solved -> stop
      const statusEl = document.querySelector(RECAPTCHA_STATUS);
      if (statusEl && statusEl.innerText && statusEl.innerText.trim().length > 0) {
        // note: this may change text; we just log
        log('Recaptcha status text: ' + statusEl.innerText.slice(0,120));
      }

      // if automated-queries shown -> stop
      const dos = document.querySelector(DOSCAPTCHA);
      if (dos && dos.innerText && dos.innerText.length > 3) {
        log('Detected automated-queries / dos message: ' + dos.innerText.slice(0,120));
        clearInterval(mainInterval);
        return;
      }

      // Attempt to click checkbox (must run IN the recaptcha iframe)
      const cb = findFirst(CHECKBOX_CANDS);
      if (cb && cb.offsetParent !== null) {
        log('Checkbox element found — clicking it.');
        cb.click();
      }

      // If audio button is present — click it to open audio challenge
      const audioBtn = findFirst(AUDIO_BUTTON_CANDS);
      if (audioBtn && audioBtn.offsetParent !== null) {
        log('Audio-button found — clicking it.');
        try { audioBtn.click(); } catch(e) { log('Click audio button failed: ' + e.message); }
      }

      // After audio opened, find audio src
      const src = discoverAudioSrc();
      if (src && src !== audioUrl && !waitingForAudioResponse) {
        audioUrl = src;
        log('Found new audio src: ' + audioUrl.slice(0,100));
        waitingForAudioResponse = true;
        requestCount = 0;
        // choose server by latency first
        const {url, idx} = chooseServer();
        log(`Selected server ${url} (idx ${idx}) — sending audio`);
        // try servers sequentially
        testServersWithAudio(audioUrl);
        waitingForAudioResponse = false;
      }

    } catch (err) {
      log('Main loop exception: ' + err.message);
      clearInterval(mainInterval);
    }
  }, 1500);

  // initial auto-ping
  for (let i = 0; i < serversList.length; i++) {
    pingServer(i);
  }

  // initial dump for debugging
  setTimeout(() => dumpSelectors(), 800);

  // small helper to instruct user
  log('Script loaded. Если не работает — нажми "Ping" и "Test" чтобы увидеть ответы серверов и dump DOM.');
  log('Важно: скрипт должен выполняться в recaptcha iframe (https://www.google.com/recaptcha/api2/*).');

})();