super-stealth

This commit is contained in:
nafer committed 2026-03-10 12:30:16 +03:00
1 parent 2a058623a4
commit 692f4c0dd1
5 files changed
+35525 -178

No files matched your search

+413 -9
View File
@@ -19,6 +19,25 @@ REMOTE_DEBUGGING_ENABLED = os.getenv("REMOTE_DEBUGGING_ENABLED", "false").lower(
# Модель по умолчанию из переменной окружения
DEFAULT_MODEL = os.getenv("LLM_MODEL_NAME", "openai/gpt-4o-mini")
# ===========================================
# Настройки прокси
# ===========================================
PROXY_URL = os.getenv("PROXY_URL", "")
CLOUD_PROXY_COUNTRY = os.getenv("CLOUD_PROXY_COUNTRY", "")
# ===========================================
# Настройки stealth
# ===========================================
BROWSER_HEADLESS = os.getenv("BROWSER_HEADLESS", "false").lower() == "true"
USE_XVFB = os.getenv("USE_XVFB", "true").lower() == "true"
VIEWPORT_SIZE = os.getenv("VIEWPORT_SIZE", "") # Формат: WIDTHxHEIGHT, напр. 1920x1080
# ===========================================
# Настройки решения капчи (CapSolver)
# ===========================================
CAPSOLVER_API_KEY = os.getenv("CAPSOLVER_API_KEY", "")
AUTO_SOLVE_CAPTCHA = os.getenv("AUTO_SOLVE_CAPTCHA", "false").lower() == "true"
from contextlib import asynccontextmanager
import logging
@@ -114,7 +133,193 @@ try:
except Exception as e2:
return ActionResult(error=f"Failed to paste text: {e2}")
logger.info("Custom tools (paste_text) registered successfully")
@custom_tools.action(description='Solve CAPTCHA using CapSolver API. Use this when you encounter reCAPTCHA, hCaptcha, or Cloudflare Turnstile. The agent will automatically detect and solve the captcha.')
async def solve_captcha(browser_session: BrowserSession = None):
"""
Решает капчу (reCAPTCHA/hCaptcha/Turnstile) с помощью CapSolver API.
Этот action автоматически:
1. Обнаруживает тип капчи на странице
2. Отправляет задачу в CapSolver
3. Ждёт решения
4. Вставляет ответ в форму
"""
if not CAPSOLVER_API_KEY:
return ActionResult(
error="CapSolver API key not configured. Set CAPSOLVER_API_KEY environment variable.",
include_in_memory=True
)
try:
page = await browser_session.get_current_page()
# Ищем sitekey для разных типов капчи
sitekey = None
captcha_type = None
# reCAPTCHA v2
recaptcha_element = await page.query_selector('[data-sitekey]')
if recaptcha_element:
sitekey = await recaptcha_element.get_attribute('data-sitekey')
if sitekey:
# Проверяем reCAPTCHA vs hCaptcha
iframe = await page.query_selector('iframe[src*="google.com/recaptcha"]')
if iframe:
captcha_type = "RecaptchaV2Task"
# hCaptcha
if not sitekey:
hcaptcha_element = await page.query_selector('[data-sitekey]')
if hcaptcha_element:
sitekey = await hcaptcha_element.get_attribute('data-sitekey')
hcaptcha_frame = await page.query_selector('iframe[src*="hcaptcha.com"]')
if hcaptcha_frame and sitekey:
captcha_type = "HCaptchaTask"
# Cloudflare Turnstile
if not sitekey:
turnstile_element = await page.query_selector('[data-sitekey]')
if turnstile_element:
sitekey = await turnstile_element.get_attribute('data-sitekey')
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
if turnstile_frame and sitekey:
captcha_type = "TurnstileTask"
if not sitekey or not captcha_type:
return ActionResult(
extracted_content="No captcha found on current page",
include_in_memory=True
)
page_url = page.url
logger.info(f"Found {captcha_type} with sitekey: {sitekey}")
# Отправляем задачу в CapSolver
import httpx
async with httpx.AsyncClient(timeout=60.0) as client:
# Создаём задачу
task_create_response = await client.post(
"https://api.capsolver.com/createTask",
json={
"clientKey": CAPSOLVER_API_KEY,
"task": {
"type": captcha_type,
"websiteURL": page_url,
"websiteKey": sitekey,
}
}
)
task_data = task_create_response.json()
if task_data.get("errorCode"):
logger.error(f"CapSolver createTask error: {task_data}")
return ActionResult(
error=f"CapSolver error: {task_data.get('errorDescription', 'Unknown error')}",
include_in_memory=True
)
task_id = task_data.get("taskId")
logger.info(f"CapSolver task created: {task_id}")
# Ждём решения (максимум 60 секунд)
solution = None
for attempt in range(30):
await asyncio.sleep(2)
result_response = await client.post(
"https://api.capsolver.com/getTaskResult",
json={
"clientKey": CAPSOLVER_API_KEY,
"taskId": task_id
}
)
result_data = result_response.json()
if result_data.get("status") == "ready":
solution = result_data.get("solution", {})
break
elif result_data.get("errorCode"):
logger.error(f"CapSolver error: {result_data}")
break
if not solution:
return ActionResult(
error="CapSolver timeout - captcha not solved",
include_in_memory=True
)
# Вставляем решение
if captcha_type == "RecaptchaV2Task":
g_recaptcha_response = solution.get("gRecaptchaResponse")
if g_recaptcha_response:
await page.evaluate(f'''() => {{
const textarea = document.getElementById('g-recaptcha-response');
if (textarea) {{
textarea.value = '{g_recaptcha_response}';
textarea.style.display = 'block';
}}
}}''')
logger.info("reCAPTCHA solved successfully!")
return ActionResult(
extracted_content="reCAPTCHA solved successfully!",
include_in_memory=True
)
elif captcha_type == "HCaptchaTask":
h_captcha_response = solution.get("hCaptchaResponse")
if h_captcha_response:
await page.evaluate(f'''() => {{
const textarea = document.querySelector('[name="h-captcha-response"]');
if (textarea) {{
textarea.value = '{h_captcha_response}';
textarea.style.display = 'block';
}}
}}''')
logger.info("hCaptcha solved successfully!")
return ActionResult(
extracted_content="hCaptcha solved successfully!",
include_in_memory=True
)
elif captcha_type == "TurnstileTask":
token = solution.get("token")
if token:
await page.evaluate(f'''(token) => {{
const textarea = document.querySelector('[name="cf-turnstile-response"]');
if (textarea) {{
textarea.value = token;
textarea.style.display = 'block';
}}
// Also try to submit to Turnstile directly
const turnstile = window.__cf_challenge_js;
if (turnstile) {{
try {{
turnstile.submit(token);
}} catch(e) {{}}
}}
}}''', token)
logger.info("Turnstile solved successfully!")
return ActionResult(
extracted_content="Turnstile solved successfully!",
include_in_memory=True
)
return ActionResult(
error="Failed to apply captcha solution",
include_in_memory=True
)
except Exception as e:
logger.error(f"Error solving captcha: {e}")
return ActionResult(
error=f"Error solving captcha: {str(e)}",
include_in_memory=True
)
logger.info("Custom tools (paste_text, solve_captcha) registered successfully")
except ImportError as e:
custom_tools = None
logger.warning(f"Could not import Tools/BrowserSession from browser_use: {e}. Custom tools disabled.")
@@ -146,6 +351,150 @@ def patch_chrome_binary():
except Exception as e:
logger.warning(f"Could not patch {path}: {e}")
async def solve_captcha_with_capsolver(page, capsolver_api_key: str) -> bool:
"""
Решает капчу (reCAPTCHA/hCaptcha/Turnstile) с помощью CapSolver API.
Args:
page: Playwright page object
capsolver_api_key: API ключ CapSolver
Returns:
True если капча решена, False иначе
"""
import httpx
if not capsolver_api_key:
logger.warning("CapSolver API key not provided")
return False
try:
# Ищем sitekey
# reCAPTCHA v2
recaptcha_element = await page.query_selector('*[name="recaptcha-sitekey"]')
if not recaptcha_element:
recaptcha_element = await page.query_selector('[data-sitekey]')
sitekey = None
captcha_type = None
if recaptcha_element:
sitekey = await recaptcha_element.get_attribute('data-sitekey')
if sitekey:
captcha_type = "RecaptchaV2Task"
# hCaptcha
if not sitekey:
hcaptcha_element = await page.query_selector('[data-sitekey]')
if hcaptcha_element:
sitekey = await hcaptcha_element.get_attribute('data-sitekey')
if sitekey:
# Проверяем что это hCaptcha
hcaptcha_frame = await page.query_selector('iframe[src*="hcaptcha.com"]')
if hcaptcha_frame:
captcha_type = "HCaptchaTask"
# Cloudflare Turnstile
if not sitekey:
turnstile_element = await page.query_selector('[data-sitekey]')
if turnstile_element:
sitekey = await turnstile_element.get_attribute('data-sitekey')
if sitekey:
turnstile_frame = await page.query_selector('iframe[src*="turnstile"]')
if turnstile_frame:
captcha_type = "TurnstileTask"
if not sitekey or not captcha_type:
logger.info("No captcha found on page")
return False
page_url = page.url
logger.info(f"Found {captcha_type} with sitekey: {sitekey}")
# Отправляем задачу в CapSolver
async with httpx.AsyncClient(timeout=60.0) as client:
# Создаём задачу
task_create_response = await client.post(
"https://api.capsolver.com/createTask",
json={
"clientKey": capsolver_api_key,
"task": {
"type": captcha_type,
"websiteURL": page_url,
"websiteKey": sitekey,
}
}
)
task_data = task_create_response.json()
if task_data.get("errorCode"):
logger.error(f"CapSolver createTask error: {task_data}")
return False
task_id = task_data.get("taskId")
logger.info(f"CapSolver task created: {task_id}")
# Ждём решения
for attempt in range(30):
await asyncio.sleep(2)
result_response = await client.post(
"https://api.capsolver.com/getTaskResult",
json={
"clientKey": capsolver_api_key,
"taskId": task_id
}
)
result_data = result_response.json()
if result_data.get("status") == "ready":
solution = result_data.get("solution", {})
if captcha_type == "RecaptchaV2Task":
# Вставляем решение в textarea
g_recaptcha_response = solution.get("gRecaptchaResponse")
if g_recaptcha_response:
# Ищем textarea для ответа
textarea = await page.query_selector('#g-recaptcha-response')
if textarea:
await textarea.fill(g_recaptcha_response)
logger.info("reCAPTCHA solved successfully!")
return True
elif captcha_type == "HCaptchaTask":
h_captcha_response = solution.get("hCaptchaResponse")
if h_captcha_response:
# Вставляем в h-captcha-response
textarea = await page.query_selector('[name="h-captcha-response"]')
if textarea:
await textarea.fill(h_captcha_response)
logger.info("hCaptcha solved successfully!")
return True
elif captcha_type == "TurnstileTask":
token = solution.get("token")
if token:
# Вставляем токен
textarea = await page.query_selector('[name="cf-turnstile-response"]')
if textarea:
await textarea.fill(token)
logger.info("Turnstile solved successfully!")
return True
return True
elif result_data.get("errorCode"):
logger.error(f"CapSolver error: {result_data}")
return False
logger.warning("CapSolver timeout - captcha not solved")
return False
except Exception as e:
logger.error(f"Error solving captcha: {e}")
return False
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup logic
@@ -371,12 +720,63 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
# Внутренний порт для Chrome
CHROME_INTERNAL_PORT = REMOTE_DEBUGGING_PORT + 1 # 9223
# ===========================================
# Настройка viewport (рандомизация)
# ===========================================
if VIEWPORT_SIZE and 'x' in VIEWPORT_SIZE:
try:
width, height = map(int, VIEWPORT_SIZE.split('x'))
except:
width, height = 1920, 1080
else:
# Рандомизация viewport для большей уникальности
import random
# Популярные разрешения
viewports = [
(1920, 1080),
(1366, 768),
(1536, 864),
(1440, 900),
(1280, 720),
(2560, 1440),
(3840, 2160),
]
width, height = random.choice(viewports)
window_size = f"{width},{height}"
# Рандомизация user-agent
import random
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
]
user_agent = random.choice(user_agents)
# ===========================================
# Настройка прокси
# ===========================================
proxy_args = []
if PROXY_URL:
# Добавляем прокси в аргументы Chrome
proxy_args = [f"--proxy-server={PROXY_URL}"]
logger.info(f"[{task_id}] Using proxy: {PROXY_URL}")
# Запускаем Chrome с remote debugging на внутреннем порту
# Используем xvfb-run для запуска в "headful" режиме (более скрытно)
# Добавляем расширенные флаги для обхода антифрода
chrome_cmd = [
"xvfb-run",
"--server-args=-screen 0 1920x1080x24",
chrome_cmd = []
# xvfb если включено
if USE_XVFB:
chrome_cmd.extend([
"xvfb-run",
f"--server-args=-screen 0 {width}x{height}x24",
])
chrome_cmd.extend([
chrome_exe,
f"--remote-debugging-port={CHROME_INTERNAL_PORT}",
"--remote-debugging-address=127.0.0.1",
@@ -387,9 +787,9 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
"--disable-dev-shm-usage",
"--no-first-run",
"--no-default-browser-check",
"--disable-blink-features=AutomationControlled", # Скрывает navigator.webdriver
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"--window-size=1920,1080",
"--disable-blink-features=AutomationControlled",
f"--user-agent={user_agent}",
f"--window-size={window_size}",
"--lang=ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
"--disable-infobars",
"--password-store=basic",
@@ -413,8 +813,12 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
"--test-type",
"--load-extension=/app/stealth-extension",
"--disable-extensions-except=/app/stealth-extension",
"about:blank"
]
])
# Добавляем прокси если есть
chrome_cmd.extend(proxy_args)
chrome_cmd.append("about:blank")
logger.info(f"[{task_id}] Executing Chrome: {' '.join(chrome_cmd)}")