Recaptcha Solver — Debuggable (Draggable Overlay + Test)

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

// ==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/*).');

})();