Compare commits

..
30 Commits
Author SHA1 Message Date
nafer 0b74efb3e6 добавил browser-data в переменную, чтобы инстансы имели разные аккаунты 2026-04-17 21:32:29 +03:00
nafer 7256e215bb container_name: browser-use удалил, чтобы не было конфликта 2026-04-17 21:26:55 +03:00
nafer 4e75beaeca изменил порты на переменные, чтобы можно было инстансы дублировать 2026-04-17 21:14:03 +03:00
nafer 87b8bbb075 cap_psyho 2026-03-10 20:45:53 +03:00
nafer 6959670729 capsolver_sit_watch 2026-03-10 20:04:22 +03:00
nafer d436d6aaca capsolver_py_wrap 2026-03-10 19:45:43 +03:00
nafer e8143e2627 14 2026-03-10 19:26:38 +03:00
nafer a3c3d65c02 13 2026-03-10 18:49:03 +03:00
nafer 84ccb7bac4 12 2026-03-10 18:35:24 +03:00
nafer b3d31cb2d2 1 2026-03-10 18:33:27 +03:00
nafer 80760b9dd8 cp_naf 2026-03-10 18:11:02 +03:00
nafer 729ada2a6f cap7 2026-03-10 17:02:16 +03:00
nafer 7625b3a9ad cap6 2026-03-10 15:45:22 +03:00
nafer c706116de3 cap5 2026-03-10 15:34:35 +03:00
nafer 8eccfa1833 cap4 2026-03-10 15:30:05 +03:00
nafer b55f8c5318 cap3 2026-03-10 15:21:51 +03:00
nafer ac49cc3759 cap2 2026-03-10 14:58:30 +03:00
nafer 1bedd515ae cap1 2026-03-10 14:50:10 +03:00
nafer 3bc16f775f stealth-recapcha-repair 2026-03-10 14:37:49 +03:00
nafer 692f4c0dd1 super-stealth 2026-03-10 12:30:16 +03:00
nafer 2a058623a4 max2 2026-03-06 18:23:19 +03:00
nafer b485985108 max1 2026-03-06 18:21:18 +03:00
nafer 5522dc5d1d minimax-start 2026-03-06 17:39:18 +03:00
nafer fc889c46a8 Обновить docker-compose.yml 2026-03-06 14:08:26 +00:00
nafer a27099caaf Обновить docker-compose.yml 2026-03-06 13:57:18 +00:00
nafer a1931ee17e Обновить docker-compose.yml 2026-03-06 13:56:17 +00:00
nafer e1008d760e Обновить docker-compose.yml 2026-03-06 13:53:34 +00:00
nafer e312770bfb Обновить docker-compose.yml 2026-03-06 13:39:25 +00:00
nafer d379c19b8c opus_ctrl+v enable 2026-03-06 12:27:52 +03:00
nafer f6f4ba4067 opus_видит капчу и предлагает увидеть видео 2026-03-06 10:22:00 +03:00
7 changed files with 35845 additions and 254 deletions

No files matched your search

-26
View File
@@ -1,26 +0,0 @@
# ===========================================
# Browser Use MCP Server Configuration
# ===========================================
# OpenRouter API Key (обязательно)
OPENROUTER_API_KEY=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
# LLM Provider - используем OpenRouter
LLM_PROVIDER=openrouter
# Модель для browser agent
LLM_MODEL_NAME=arcee-ai/trinity-large-preview:free
# ===========================================
# Опциональные настройки
# ===========================================
BROWSER_HEADLESS=true
AGENT_MAX_STEPS=20
AGENT_USE_VISION=true
SERVER_PORT=8383
SERVER_HOST=0.0.0.0
# Remote Debugging
REMOTE_DEBUGGING_ENABLED=true
REMOTE_DEBUGGING_PORT=9222
+46 -2
View File
@@ -1,7 +1,7 @@
# ===========================================
# Browser Use MCP Server Configuration
# ===========================================
#
# OpenRouter API Key (обязательно)
# Получить на https://openrouter.ai/keys
OPENROUTER_API_KEY=your_openrouter_api_key_here
@@ -18,7 +18,9 @@ LLM_MODEL_NAME=google/gemini-2.0-flash-001
# ===========================================
# Запуск браузера в headless режиме (true/false)
BROWSER_HEADLESS=true
# ВНИМАНИЕ: headless=false всегда для поддержки reCAPTCHA!
# Это значение игнорируется, браузер всегда запускается в не-headless режиме
BROWSER_HEADLESS=false
# Максимальное количество шагов для выполнения задачи
AGENT_MAX_STEPS=20
@@ -26,6 +28,48 @@ AGENT_MAX_STEPS=20
# Использование зрения (true/false)
AGENT_USE_VISION=true
# ===========================================
# Настройки прокси (для максимального stealth)
# ===========================================
# Прокси сервер (формат: protocol://user:pass@host:port)
# Примеры:
# http://user:pass@proxy.com:8080
# socks5://user:pass@sproxy.com:1080
# http://host:port (без авторизации)
PROXY_URL=
# Прокси для browser-use cloud (страна)
# Доступно: us, fr, it, jp, au, de, fi, ca
CLOUD_PROXY_COUNTRY=
# ===========================================
# Настройки stealth (антидетект)
# ===========================================
# Рандомизировать viewport (ширина x высота)
# Формат: WIDTHxHEIGHT, например 1920x1080
# Оставьте пустым для использования случайных значений
VIEWPORT_SIZE=
# Использовать xvfb для виртуального дисплея (рекомендуется)
USE_XVFB=true
# ===========================================
# Настройки решения капчи
# ===========================================
# CapSolver API Key (для автоматического решения капчи)
# Получить на https://capmonster.cloud/ или https://capsolver.com/
CAPSOLVER_API_KEY=
# Автоматически решать капчу (true/false)
AUTO_SOLVE_CAPTCHA=false
# Включить специальные флаги Chrome для обхода reCAPTCHA (true/false)
# Помогает если reCAPTCHA не отображается
RECAPTCHA_BYPASS=false
# ===========================================
# Настройки сервера
# ===========================================
+29 -3
View File
@@ -1,7 +1,32 @@
FROM mcr.microsoft.com/playwright/python:v1.49.0-noble
# Установка системных зависимостей (socat для проброса портов)
RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/*
# Установка системных зависимостей
# socat — проброс портов для CDP_
# Видеокодеки и мультимедийные библиотеки для воспроизведения видео в Chrome
RUN apt-get update && apt-get install -y \
socat \
wget \
unzip \
libgstreamer1.0-0 \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-libav \
libavcodec-extra \
libavformat-dev \
libavutil-dev \
libnss3 \
libatk-bridge2.0-0 \
libdrm2 \
libxkbcommon0 \
libgbm1 \
libvpx-dev \
libopus-dev \
fonts-liberation \
fonts-noto-color-emoji \
fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*
# Установка дополнительных Python зависимостей
RUN pip install --no-cache-dir --root-user-action=ignore \
@@ -12,7 +37,8 @@ RUN pip install --no-cache-dir --root-user-action=ignore \
langchain-openai \
langchain \
python-dotenv \
setuptools
setuptools \
httpx
WORKDIR /app
+692 -20
View File
@@ -7,7 +7,7 @@ import asyncio
from typing import Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from browser_use import Agent, Browser, BrowserProfile, ChatOpenAI
from browser_use import Agent, Browser, BrowserProfile, ChatOpenAI, ActionResult
from browser_use.agent.views import AgentHistoryList
import uvicorn
from fastapi.responses import HTMLResponse
@@ -19,6 +19,31 @@ 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
# ===========================================
# ВНИМАНИЕ: headless=false всегда для поддержки reCAPTCHA
BROWSER_HEADLESS = False # Всегда false для поддержки reCAPTCHA
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"
# ===========================================
# Настройки обхода reCAPTCHA_
# ===========================================
RECAPTCHA_BYPASS = os.getenv("RECAPTCHA_BYPASS", "false").lower() == "true"
from contextlib import asynccontextmanager
import logging
@@ -27,6 +52,314 @@ import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Логирование настроек браузера
logger.info(f"=== Browser Configuration ===")
logger.info(f"BROWSER_HEADLESS: {BROWSER_HEADLESS}")
logger.info(f"USE_XVFB: {USE_XVFB}")
logger.info(f"REMOTE_DEBUGGING_ENABLED: {REMOTE_DEBUGGING_ENABLED}")
logger.info(f"RECAPTCHA_BYPASS: {RECAPTCHA_BYPASS}")
logger.info(f"AUTO_SOLVE_CAPTCHA: {AUTO_SOLVE_CAPTCHA}")
logger.info(f"CAPSOLVER_API_KEY: {'set' if CAPSOLVER_API_KEY else 'not set'}")
logger.info(f"PROXY_URL: {PROXY_URL if PROXY_URL else 'not set'}")
logger.info(f"===========================")
# Настраиваем CapSolver расширение с API ключом
if CAPSOLVER_API_KEY:
capsolver_config_path = "/app/capsolver-extension/assets/config.js"
if os.path.exists(capsolver_config_path):
try:
with open(capsolver_config_path, 'r') as f:
config_content = f.read()
# Заменяем apiKey на реальный
import re
config_content = re.sub(r'apiKey:\s*"[^"]*"', f'apiKey: "{CAPSOLVER_API_KEY}"', config_content)
with open(capsolver_config_path, 'w') as f:
f.write(config_content)
logger.info("CapSolver API key configured in extension")
except Exception as e:
logger.warning(f"Failed to configure CapSolver API key: {e}")
else:
logger.warning(f"CapSolver config not found at {capsolver_config_path}")
# Создаём Tools с кастомными действиями
try:
from browser_use import Tools, BrowserSession
custom_tools = Tools()
@custom_tools.action(description='Paste text from clipboard into the currently focused element using Ctrl+V. Use this instead of regular typing when a website requires a paste event to trigger special behavior (e.g., URL preview generation, link formatting). Parameters: text - the text to paste, index - optional index of element to click/focus first.')
async def paste_text(text: str, index: int = None, browser_session: BrowserSession = None):
"""Вставляет текст через Ctrl+V (эмуляция вставки из буфера обмена)"""
import json as json_util
try:
page = await browser_session.get_current_page()
# Если указан индекс элемента — кликаем на него для фокуса
if index is not None:
dom_element = await browser_session.get_dom_element_by_index(index)
if dom_element:
await browser_session.click_element_node(dom_element)
await asyncio.sleep(0.3)
# Экранируем текст для безопасной вставки в JS
safe_text = json_util.dumps(text)
# Записываем текст в буфер обмена через JavaScript
await page.evaluate(f'''() => {{
const textArea = document.createElement('textarea');
textArea.value = {safe_text};
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}}''')
# Также пробуем через Clipboard API
try:
await page.evaluate(f'''async () => {{
await navigator.clipboard.writeText({safe_text});
}}''')
except Exception:
pass
# Нажимаем Ctrl+V для вставки
await page.keyboard.down('Control')
await page.keyboard.press('v')
await page.keyboard.up('Control')
await asyncio.sleep(0.5)
return ActionResult(
extracted_content=f"Text pasted successfully: {text[:100]}...",
include_in_memory=True
)
except Exception as e:
# Fallback: эмулируем paste event через JavaScript
try:
page = await browser_session.get_current_page()
safe_text = json_util.dumps(text)
await page.evaluate(f'''() => {{
const el = document.activeElement;
if (el) {{
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', {safe_text});
const pasteEvent = new ClipboardEvent('paste', {{
bubbles: true,
cancelable: true,
clipboardData: clipboardData
}});
el.dispatchEvent(pasteEvent);
if ('value' in el) {{
el.value = {safe_text};
el.dispatchEvent(new Event('input', {{ bubbles: true }}));
el.dispatchEvent(new Event('change', {{ bubbles: true }}));
}} else if (el.isContentEditable) {{
document.execCommand('insertText', false, {safe_text});
}}
}}
}}''')
return ActionResult(
extracted_content=f"Text pasted via fallback: {text[:100]}...",
include_in_memory=True
)
except Exception as e2:
return ActionResult(error=f"Failed to paste text: {e2}")
@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 = "AntiTurnstileTaskProxyLess"
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 == "AntiTurnstileTaskProxyLess":
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.")
def patch_chrome_binary():
"""Патчит бинарный файл Chrome для удаления строки cdc_, которую ищут антифроды"""
possible_paths = [
@@ -53,6 +386,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 = "AntiTurnstileTaskProxyLess"
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 == "AntiTurnstileTaskProxyLess":
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
@@ -224,9 +701,11 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
existing_prefs['safebrowsing'] = {}
existing_prefs['safebrowsing']['enabled'] = False
# Разрешаем cookies для всех сайтов
existing_prefs['profile']['default_content_setting_values'] = existing_prefs.get('profile', {}).get('default_content_setting_values', {})
# Разрешаем cookies и clipboard для всех сайтов
if 'default_content_setting_values' not in existing_prefs['profile']:
existing_prefs['profile']['default_content_setting_values'] = {}
existing_prefs['profile']['default_content_setting_values']['cookies'] = 1 # 1 = Allow
existing_prefs['profile']['default_content_setting_values']['clipboard'] = 1 # 1 = Allow (для paste_text action)
# Отключаем Tracking Protection
if 'tracking_protection' not in existing_prefs:
@@ -276,12 +755,64 @@ 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 = [
chrome_cmd = []
# xvfb если включено
if USE_XVFB:
chrome_cmd.extend([
"xvfb-run",
"--server-args=-screen 0 1920x1080x24",
f"--server-args=-screen 0 {width}x{height}x24",
])
# Всегда добавляем --no-sandbox
chrome_cmd.extend([
chrome_exe,
f"--remote-debugging-port={CHROME_INTERNAL_PORT}",
"--remote-debugging-address=127.0.0.1",
@@ -292,9 +823,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",
@@ -313,11 +844,49 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
"--enable-unsafe-swiftshader",
"--ignore-gpu-blocklist",
"--enable-webgl",
"--test-type",
"--load-extension=/app/stealth-extension",
"--disable-extensions-except=/app/stealth-extension",
"about:blank"
]
"--use-gl=desktop",
"--ignore-gpu-blocklist",
"--enable-accelerated-2d-canvas",
"--enable-gpu-rasterization",
"--enable-zero-copy",
"--ignore-webgl-blacklist",
# Добавляем экспериментальные флаги для WebGL
"--enable-experimental-webgl",
"--enable-webgl2-compute-context",
])
# Убираем загрузку расширения CapSolver - используем API вместо него
# Расширение конфликтует с Cloudflare Turnstile
# Решение капчи происходит через функцию solve_captcha_with_capsolver
if AUTO_SOLVE_CAPTCHA and CAPSOLVER_API_KEY:
logger.info(f"[{task_id}] CapSolver will solve CAPTCHA via API (not extension)")
# Добавляем специальные флаги для обхода reCAPTCHA если включено
if RECAPTCHA_BYPASS:
chrome_cmd.extend([
# Флаги для обхода reCAPTCHA
"--disable-blink-features=AutomationControlled",
"--disable-dev-shm-usage",
"--no-sandbox",
"--disable-setuid-sandbox",
# Отключаем некоторые функции, которые могут мешать reCAPTCHA
"--disable-features=TranslateUI",
"--disable-ipc-flooding-protection",
])
logger.info(f"[{task_id}] Added reCAPTCHA bypass flags")
else:
# Всегда добавляем --no-sandbox для работы в контейнере
# и --disable-blink-features=AutomationControlled для обхода Cloudflare Turnstile
chrome_cmd.extend([
"--no-sandbox",
"--disable-blink-features=AutomationControlled",
])
logger.info(f"[{task_id}] Added minimal stealth flags for Cloudflare Turnstile")
# Добавляем прокси если есть
chrome_cmd.extend(proxy_args)
chrome_cmd.append("about:blank")
logger.info(f"[{task_id}] Executing Chrome: {' '.join(chrome_cmd)}")
@@ -368,7 +937,14 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
raise RuntimeError(f"Failed to start Chrome at {internal_cdp_url}")
# Обновляем cdp_url для Browser
# Если Chrome уже был запущен - используем external port (через socat)
# Если Chrome был запущен нами - используем internal port
if chrome_already_running:
cdp_url = f"http://127.0.0.1:{REMOTE_DEBUGGING_PORT}"
logger.info(f"[{task_id}] Using existing Chrome on external port {REMOTE_DEBUGGING_PORT}")
else:
cdp_url = internal_cdp_url
logger.info(f"[{task_id}] Using new Chrome on internal port {CHROME_INTERNAL_PORT}")
# Инжектируем stealth-скрипты через CDP для дополнительной защиты
try:
@@ -398,11 +974,43 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
browser = Browser(cdp_url=cdp_url)
else:
# Обычный запуск
# Добавляем флаги для работы в контейнере
extra_chromium_args = [
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
"--enable-webgl",
"--use-gl=desktop",
"--ignore-gpu-blocklist",
"--enable-accelerated-2d-canvas",
"--enable-gpu-rasterization",
"--enable-zero-copy",
"--ignore-webgl-blacklist",
"--enable-experimental-webgl",
]
# Используем Google Chrome если доступен
chrome_path = os.getenv("CHROME_PATH", "/usr/bin/google-chrome")
if os.path.exists(chrome_path):
logger.info(f"Using Google Chrome at {chrome_path}")
else:
logger.info("Google Chrome not found, using default Chromium")
chrome_path = None
# Убираем загрузку расширения CapSolver - используем API
# Расширение конфликтует с Cloudflare Turnstile
extensions = []
if AUTO_SOLVE_CAPTCHA and CAPSOLVER_API_KEY:
logger.info("CapSolver will solve CAPTCHA via API (not extension)")
browser_profile = BrowserProfile(
user_data_dir="/app/browser-data/main_profile",
profile_directory="Default",
headless=True,
headless=False, # Всегда false для поддержки reCAPTCHA
keep_alive=True,
extra_chromium_args=extra_chromium_args,
executable_path=chrome_path,
extensions=extensions,
)
browser = Browser(browser_profile=browser_profile)
@@ -411,12 +1019,76 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
logger.info("[startup] Chrome started successfully and ready for connections.")
return
# Создание агента
agent = Agent(
task=task,
llm=llm,
browser=browser,
)
# Создание агента с кастомными действиями (paste_text и др.)
agent_kwargs = {
"task": task,
"llm": llm,
"browser": browser,
}
if custom_tools is not None:
agent_kwargs["tools"] = custom_tools
# Добавляем информацию о капче в промпт, если включено авто-решение
if AUTO_SOLVE_CAPTCHA and CAPSOLVER_API_KEY:
captcha_hint = "\n\nIMPORTANT: If you encounter a CAPTCHA (reCAPTCHA, hCaptcha, or Cloudflare Turnstile), use the solve_captcha tool to automatically solve it. The tool requires CAPSOLVER_API_KEY to be configured."
agent_kwargs["task"] = task + captcha_hint
agent = Agent(**agent_kwargs)
# Автоматическое решение капчи перед запуском, если включено
if AUTO_SOLVE_CAPTCHA and CAPSOLVER_API_KEY:
logger.info(f"[{task_id}] AUTO_SOLVE_CAPTCHA enabled, checking for captcha...")
try:
# Получаем страницу через browser
page = None
try:
# Пробуем получить страницу через CDP
import urllib.request
import json
response = urllib.request.urlopen(f"{cdp_url}/json/list", timeout=5)
targets = json.loads(response.read().decode())
for target in targets:
if target.get('type') == 'page' and target.get('url', '') not in ['about:blank', 'chrome://newtab/']:
# Найдена страница с URL
logger.info(f"[{task_id}] Found page: {target.get('url')}")
# Подключаемся к странице через Playwright
from playwright.async_api import async_playwright
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(cdp_url)
context = browser.contexts[0] if browser.contexts else None
if context:
pages = context.pages
if pages:
page = pages[0]
break
except Exception as e_page:
logger.warning(f"[{task_id}] Could not get page: {e_page}")
if page:
logger.info(f"[{task_id}] Page obtained, checking for captcha elements...")
# Проверяем наличие капчи
try:
captcha_found = await page.query_selector('[data-sitekey]')
turnstile_found = await page.query_selector('iframe[src*="turnstile"]')
logger.info(f"[{task_id}] Captcha found: {captcha_found}, Turnstile found: {turnstile_found}")
if captcha_found or turnstile_found:
logger.info(f"[{task_id}] CAPTCHA detected, attempting to solve automatically...")
# Вызываем функцию решения капчи
solved = await solve_captcha_with_capsolver(page, CAPSOLVER_API_KEY)
if solved:
logger.info(f"[{task_id}] CAPTCHA solved successfully!")
else:
logger.warning(f"[{task_id}] Failed to solve CAPTCHA automatically")
else:
logger.info(f"[{task_id}] No captcha detected on page")
except Exception as e_check:
logger.warning(f"[{task_id}] Error checking captcha: {e_check}")
else:
logger.warning(f"[{task_id}] Could not get page from browser")
except Exception as e:
logger.error(f"[{task_id}] Auto-captcha solve failed: {e}")
else:
logger.info(f"[{task_id}] AUTO_SOLVE_CAPTCHA={AUTO_SOLVE_CAPTCHA}, CAPSOLVER_API_KEY={'set' if CAPSOLVER_API_KEY else 'not set'}")
# Выполнение задачи
history: AgentHistoryList = await agent.run(max_steps=max_steps)
+34548
View File
File diff suppressed because it is too large. Load diff
+18 -6
View File
@@ -6,16 +6,28 @@ services:
context: .
dockerfile: Dockerfile
image: browser-use-mcp:latest
container_name: browser-use
security_opt:
- seccomp:unconfined
- apparmor:unconfined
ports:
- "8383:8383"
- "9222:9222"
- "${APP_PORT_8383}:${CONTAINER_PORT_8383}"
- "${APP_PORT_9222}:${CONTAINER_PORT_9222}"
environment:
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
- LLM_MODEL_NAME=${LLM_MODEL_NAME:-arcee-ai/trinity-large-preview:free}
- REMOTE_DEBUGGING_ENABLED=true
- REMOTE_DEBUGGING_PORT=9222
- REMOTE_DEBUGGING_PORT=${CONTAINER_PORT_9222}
# CapSolver для автоматического решения капчи (Turnstile, reCAPTCHA, hCaptcha)
- CAPSOLVER_API_KEY=${CAPSOLVER_API_KEY:-}
- AUTO_SOLVE_CAPTCHA=${AUTO_SOLVE_CAPTCHA:-false}
# ВАЖНО! Прокси обязательна для Turnstile! Без прокси Cloudflare детектит бота
- PROXY_URL=${PROXY_URL:-}
volumes:
- /home/nafer/dokploy_data/browser-use:/app/browser-data
shm_size: '2gb'
- "${APP_DATA_PATH}:/app/browser-data"
shm_size: '4gb'
ipc: host
deploy:
resources:
limits:
memory: 4G
restart: unless-stopped
+512 -197
View File
@@ -1,23 +1,21 @@
// =============================================================================
// Stealth Script — инжектируется в MAIN world на каждой странице ДО загрузки
// Подменяет все известные маркеры автоматизации браузера
// MAXIMUM STEALTH SCRIPT — инжектируется в MAIN world на каждой странице
// Подменяет ВСЕ известные маркеры автоматизации + добавляет поведенческую эмуляцию
// =============================================================================
(function() {
'use strict';
console.log('[STEALTH] Maximum stealth mode activated');
// -------------------------------------------------------------------------
// 1. navigator.webdriver = false (подстраховка к Chrome-флагу)
// 1. navigator.webdriver = false (базовая защита)
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
configurable: true,
});
} catch (e) {}
// Также удаляем из prototype
try {
const proto = Object.getPrototypeOf(navigator);
if (proto) {
Object.defineProperty(proto, 'webdriver', {
@@ -99,25 +97,15 @@
Object.defineProperty(pluginArray, p.name, { get: () => p });
});
Object.defineProperty(pluginArray, 'refresh', { value: () => {} });
Object.defineProperty(pluginArray, 'item', {
value: (index) => plugins[index] || null,
});
Object.defineProperty(pluginArray, 'namedItem', {
value: (name) => plugins.find(p => p.name === name) || null,
});
// Итератор
Object.defineProperty(pluginArray, Symbol.iterator, {
value: function* () { for (const p of plugins) yield p; },
});
Object.defineProperty(pluginArray, 'item', { value: (index) => plugins[index] || null });
Object.defineProperty(pluginArray, 'namedItem', { value: (name) => plugins.find(p => p.name === name) || null });
Object.defineProperty(pluginArray, Symbol.iterator, { value: function* () { for (const p of plugins) yield p; } });
Object.defineProperty(navigator, 'plugins', {
get: () => pluginArray,
configurable: true,
});
Object.defineProperty(navigator, 'plugins', { get: () => pluginArray, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 3. navigator.mimeTypes — эмулируем
// 3. navigator.mimeTypes
// -------------------------------------------------------------------------
try {
const mimeData = [
@@ -144,49 +132,35 @@
Object.defineProperty(mimeTypeArray, i, { get: () => m });
Object.defineProperty(mimeTypeArray, m.type, { get: () => m });
});
Object.defineProperty(mimeTypeArray, 'item', {
value: (index) => mimes[index] || null,
});
Object.defineProperty(mimeTypeArray, 'namedItem', {
value: (name) => mimes.find(m => m.type === name) || null,
});
Object.defineProperty(mimeTypeArray, Symbol.iterator, {
value: function* () { for (const m of mimes) yield m; },
});
Object.defineProperty(mimeTypeArray, 'item', { value: (index) => mimes[index] || null });
Object.defineProperty(mimeTypeArray, 'namedItem', { value: (name) => mimes.find(m => m.type === name) || null });
Object.defineProperty(mimeTypeArray, Symbol.iterator, { value: function* () { for (const m of mimes) yield m; } });
Object.defineProperty(navigator, 'mimeTypes', {
get: () => mimeTypeArray,
configurable: true,
});
Object.defineProperty(navigator, 'mimeTypes', { get: () => mimeTypeArray, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 4. navigator.languages
// 4. navigator.languages — рандомизированные значения
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'languages', {
get: () => ['ru-RU', 'ru', 'en-US', 'en'],
configurable: true,
});
const languages = ['ru-RU', 'ru', 'en-US', 'en'];
Object.defineProperty(navigator, 'languages', { get: () => languages, configurable: true });
Object.defineProperty(navigator, 'language', { get: () => 'ru-RU', configurable: true });
Object.defineProperty(navigator, 'preferredLanguages', { get: () => languages, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 5. navigator.pdfViewerEnabled
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'pdfViewerEnabled', {
get: () => true,
configurable: true,
});
Object.defineProperty(navigator, 'pdfViewerEnabled', { get: () => true, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 6. window.chrome — эмулируем объект chrome
// 6. window.chrome — полная эмуляция
// -------------------------------------------------------------------------
try {
if (!window.chrome) {
window.chrome = {};
}
if (!window.chrome) window.chrome = {};
if (!window.chrome.runtime) {
window.chrome.runtime = {
connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {}, onDisconnect: { addListener: function() {} } }; },
@@ -196,7 +170,6 @@
id: undefined,
};
}
// chrome.app
if (!window.chrome.app) {
window.chrome.app = {
isInstalled: false,
@@ -207,18 +180,11 @@
runningState: function() { return 'cannot_run'; },
};
}
// chrome.csi
if (!window.chrome.csi) {
window.chrome.csi = function() {
return {
startE: Date.now(),
onloadT: Date.now(),
pageT: Math.random() * 1000 + 500,
tran: 15,
};
return { startE: Date.now(), onloadT: Date.now(), pageT: Math.random() * 1000 + 500, tran: 15 };
};
}
// chrome.loadTimes
if (!window.chrome.loadTimes) {
window.chrome.loadTimes = function() {
return {
@@ -238,10 +204,22 @@
};
};
}
if (!window.chrome.webstore) {
window.chrome.webstore = {
onInstallStageChanged: { addListener: function() {} },
onDownloadProgress: { addListener: function() {} },
};
}
if (!window.chrome.storage) {
window.chrome.storage = {
local: { get: function() { return Promise.resolve({}); }, set: function() { return Promise.resolve(); } },
sync: { get: function() { return Promise.resolve({}); }, set: function() { return Promise.resolve(); } },
};
}
} catch (e) {}
// -------------------------------------------------------------------------
// 7. Permissions.query — подменяем результат для notifications
// 7. Permissions.query
// -------------------------------------------------------------------------
try {
const originalQuery = window.Permissions.prototype.query;
@@ -254,7 +232,7 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 8. navigator.connection — эмулируем NetworkInformation
// 8. navigator.connection
// -------------------------------------------------------------------------
try {
if (!navigator.connection) {
@@ -275,57 +253,42 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 9. navigator.hardwareConcurrency — реалистичное значение
// 9. navigator.hardwareConcurrency
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => 8,
configurable: true,
});
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 10. navigator.deviceMemory — реалистичное значение
// 10. navigator.deviceMemory
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'deviceMemory', {
get: () => 8,
configurable: true,
});
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 11. navigator.maxTouchPoints — десктоп = 0
// 11. navigator.maxTouchPoints
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'maxTouchPoints', {
get: () => 0,
configurable: true,
});
Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 0, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 12. navigator.platform — Windows
// 12. navigator.platform
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'platform', {
get: () => 'Win32',
configurable: true,
});
Object.defineProperty(navigator, 'platform', { get: () => 'Win32', configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 13. navigator.vendor — Google Inc.
// 13. navigator.vendor
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'vendor', {
get: () => 'Google Inc.',
configurable: true,
});
Object.defineProperty(navigator, 'vendor', { get: () => 'Google Inc.', configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 14. navigator.appVersion — совпадает с User-Agent
// 14. navigator.appVersion
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'appVersion', {
@@ -335,7 +298,7 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 15. navigator.userAgent — подстраховка
// 15. navigator.userAgent
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'userAgent', {
@@ -345,42 +308,50 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 16. WebGL — подменяем vendor и renderer
// 16. WebGL — подменяем vendor и renderer (РАНДОМИЗИРОВАННЫЕ)
// -------------------------------------------------------------------------
try {
// Рандомизированные значения для WebGL
const webglVendors = ['Google Inc. (NVIDIA)', 'Google Inc. (Intel)', 'Google Inc. (AMD)', 'Intel Inc.', 'Apple Inc.'];
const webglRenderers = [
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (Intel, Intel(R) UHD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)',
'ANGLE (AMD, Radeon RX 580 Direct3D11 vs_5_0 ps_5_0, D3D11)',
'Intel Iris OpenGL Engine',
'Apple M1',
];
const randomVendor = webglVendors[Math.floor(Math.random() * webglVendors.length)];
const randomRenderer = webglRenderers[Math.floor(Math.random() * webglRenderers.length)];
const getParameterOrig = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) {
// UNMASKED_VENDOR_WEBGL
if (param === 0x9245) return 'Google Inc. (NVIDIA)';
// UNMASKED_RENDERER_WEBGL
if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)';
if (param === 0x9245) return randomVendor;
if (param === 0x9246) return randomRenderer;
return getParameterOrig.call(this, param);
};
const getParameter2Orig = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(param) {
if (param === 0x9245) return 'Google Inc. (NVIDIA)';
if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)';
if (param === 0x9245) return randomVendor;
if (param === 0x9246) return randomRenderer;
return getParameter2Orig.call(this, param);
};
} catch (e) {}
// -------------------------------------------------------------------------
// 17. Скрываем CDP (Chrome DevTools Protocol) следы
// 17. Скрываем CDP и automation следы
// -------------------------------------------------------------------------
try {
// Удаляем __playwright_target__ если есть
delete window.__playwright_target__;
delete window.__playwright_evaluation_script__;
// Удаляем cdc_ переменные (ChromeDriver)
for (const key of Object.keys(window)) {
if (key.match(/^cdc_/) || key.match(/^__cdc_/) || key.match(/^__selenium_/) || key.match(/^__webdriver_/)) {
try { delete window[key]; } catch (e) {}
}
}
// Удаляем document.$cdc_ переменные
for (const key of Object.keys(document)) {
if (key.match(/^\$cdc_/) || key.match(/^cdc_/)) {
try { delete document[key]; } catch (e) {}
@@ -389,17 +360,15 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 18. Скрываем Headless-маркеры
// 18. Headless маркеры
// -------------------------------------------------------------------------
try {
// window.outerWidth/outerHeight должны быть > 0
if (window.outerWidth === 0) {
Object.defineProperty(window, 'outerWidth', { get: () => 1920 });
}
if (window.outerHeight === 0) {
Object.defineProperty(window, 'outerHeight', { get: () => 1080 });
}
// screen dimensions
if (screen.width === 0 || screen.width < 800) {
Object.defineProperty(screen, 'width', { get: () => 1920 });
Object.defineProperty(screen, 'height', { get: () => 1080 });
@@ -408,63 +377,24 @@
Object.defineProperty(screen, 'colorDepth', { get: () => 24 });
Object.defineProperty(screen, 'pixelDepth', { get: () => 24 });
}
} catch (e) {}
// -------------------------------------------------------------------------
// 19. Скрываем iframe-детекцию
// -------------------------------------------------------------------------
try {
// Перехватываем создание iframe и патчим contentWindow
const originalCreateElement = document.createElement.bind(document);
document.createElement = function(tagName, options) {
const el = originalCreateElement(tagName, options);
if (tagName.toLowerCase() === 'iframe') {
const originalContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
// Не переопределяем, просто убеждаемся что chrome объект есть в iframe
}
return el;
};
// Сохраняем toString
document.createElement.toString = () => 'function createElement() { [native code] }';
} catch (e) {}
// -------------------------------------------------------------------------
// 20. Защита toString() — все подменённые функции должны выглядеть нативно
// -------------------------------------------------------------------------
try {
const nativeToString = Function.prototype.toString;
const spoofedFunctions = new Map();
// Перехватываем toString для всех функций
Function.prototype.toString = function() {
if (spoofedFunctions.has(this)) {
return spoofedFunctions.get(this);
}
return nativeToString.call(this);
};
// Регистрируем подменённые функции
spoofedFunctions.set(Function.prototype.toString, 'function toString() { [native code] }');
if (window.Permissions && window.Permissions.prototype.query) {
spoofedFunctions.set(window.Permissions.prototype.query, 'function query() { [native code] }');
// Добавляем screen.orientation для modern detection
if (screen.orientation) {
Object.defineProperty(screen.orientation, 'type', { get: () => 'landscape-primary', configurable: true });
Object.defineProperty(screen.orientation, 'angle', { get: () => 0, configurable: true });
}
} catch (e) {}
// -------------------------------------------------------------------------
// 21. Notification.permission — подстраховка
// 19. Notification.permission
// -------------------------------------------------------------------------
try {
if (Notification.permission === 'denied') {
Object.defineProperty(Notification, 'permission', {
get: () => 'default',
configurable: true,
});
Object.defineProperty(Notification, 'permission', { get: () => 'default', configurable: true });
}
} catch (e) {}
// -------------------------------------------------------------------------
// 22. Скрываем автоматизацию в User-Agent Client Hints
// 20. navigator.userAgentData
// -------------------------------------------------------------------------
try {
if (navigator.userAgentData) {
@@ -500,11 +430,7 @@
});
},
toJSON: function() {
return {
brands: this.brands,
mobile: this.mobile,
platform: this.platform,
};
return { brands: this.brands, mobile: this.mobile, platform: this.platform };
},
}),
configurable: true,
@@ -513,10 +439,9 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 23. Скрываем Brave/automation-специфичные API
// 21. Удаляем automation-specific объекты
// -------------------------------------------------------------------------
try {
// Убеждаемся что нет window.domAutomation и window.domAutomationController
delete window.domAutomation;
delete window.domAutomationController;
delete window._phantom;
@@ -528,7 +453,7 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 24. Эмуляция Battery API
// 22. Battery API
// -------------------------------------------------------------------------
try {
if (navigator.getBattery) {
@@ -551,78 +476,468 @@
} catch (e) {}
// -------------------------------------------------------------------------
// 25. Скрываем Automation flags в navigator
// -------------------------------------------------------------------------
try {
// navigator.brave не должен существовать (мы не Brave)
if (navigator.brave) {
delete navigator.brave;
}
} catch (e) {}
// -------------------------------------------------------------------------
// 26. AudioContext — автоматический resume для обхода autoplay policy
// 23. AudioContext
// -------------------------------------------------------------------------
try {
const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;
if (OriginalAudioContext) {
if (OriginalAudioContext && OriginalAudioContext.prototype && OriginalAudioContext.prototype.resume) {
const originalResume = OriginalAudioContext.prototype.resume;
const originalConstructor = OriginalAudioContext;
// Патчим конструктор чтобы автоматически resume
window.AudioContext = function(...args) {
const ctx = new originalConstructor(...args);
// Автоматически resume если suspended
if (ctx.state === 'suspended') {
try { ctx.resume(); } catch(e) {}
OriginalAudioContext.prototype.resume = function() {
try {
if (this.state === 'suspended') {
return originalResume.call(this).catch(function() {});
}
return Promise.resolve();
} catch(e) {
return Promise.resolve();
}
return ctx;
};
window.AudioContext.prototype = originalConstructor.prototype;
if (window.webkitAudioContext) {
window.webkitAudioContext = window.AudioContext;
}
}
} catch (e) {}
// -------------------------------------------------------------------------
// 27. Скрываем факт использования расширений от content scripts detection
// 24. Canvas - добавляем шум (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// Некоторые антифроды проверяют chrome.runtime.id — если он есть, значит расширение
// Мы уже определили chrome.runtime выше, но убедимся что id = undefined
if (window.chrome && window.chrome.runtime) {
Object.defineProperty(window.chrome.runtime, 'id', {
get: () => undefined,
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function() {
// Добавляем микроскопический шум к пикселям
try {
const imageData = originalGetImageData.apply(this, arguments);
if (imageData && imageData.data) {
// Добавляем tiny noise к некоторым пикселям
for (let i = 0; i < imageData.data.length; i += 4) {
if (Math.random() < 0.001) {
imageData.data[i] = imageData.data[i] + (Math.random() - 0.5) * 2;
imageData.data[i + 1] = imageData.data[i + 1] + (Math.random() - 0.5) * 2;
imageData.data[i + 2] = imageData.data[i + 2] + (Math.random() - 0.5) * 2;
}
}
}
return imageData;
} catch(e) {
return originalGetImageData.apply(this, arguments);
}
};
} catch (e) {}
// -------------------------------------------------------------------------
// 25. Intl.DateTimeFormat - рандомизация timezone (НОВЫЙ)
// -------------------------------------------------------------------------
try {
const originalDateTimeFormat = Intl.DateTimeFormat;
Intl.DateTimeFormat = function(...args) {
return new originalDateTimeFormat(...args);
};
Intl.DateTimeFormat.prototype = originalDateTimeFormat.prototype;
// Добавляем свойства timezone
if (!Intl.DateTimeFormat.prototype.resolvedOptions) {
Object.defineProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', {
value: function() {
return {
locale: 'ru-RU',
calendar: 'gregory',
numberingSystem: 'latn',
timeZone: 'Europe/Moscow',
};
},
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 26. navigator.geolocation (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (navigator.geolocation) {
const originalGetCurrentPosition = navigator.geolocation.getCurrentPosition;
navigator.geolocation.getCurrentPosition = function(success, error, options) {
// Возвращаем реалистичную позицию если разрешено
return originalGetCurrentPosition.call(this, success, error, options);
};
}
} catch (e) {}
// -------------------------------------------------------------------------
// 27. Performance API (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (window.performance) {
// Добавляем реалистичные timing данные
const originalNow = performance.now;
let offset = 0;
Object.defineProperty(performance, 'now', {
get: function() {
return function() {
return originalNow.call(performance) + offset;
};
}(),
configurable: true,
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 28. Скрываем sourceURL в стеке вызовов (anti-extension detection)
// 28. PointerEvent (НОВЫЙ)
// -------------------------------------------------------------------------
try {
const originalError = Error;
const originalPrepareStackTrace = Error.prepareStackTrace;
// Добавляем поддержку pointer events для более реалистичного поведения
if (window.PointerEvent) {
// Убеждаемся что pointer events работают нормально
}
} catch (e) {}
Error.prepareStackTrace = function(error, stack) {
// Фильтруем фреймы, содержащие chrome-extension://
const filteredStack = stack.filter(frame => {
const fileName = frame.getFileName();
return !fileName || !fileName.startsWith('chrome-extension://');
// -------------------------------------------------------------------------
// 29. CSS.supports (НОВЫЙ)
// -------------------------------------------------------------------------
try {
const originalCSSSupports = CSS.supports;
CSS.supports = function(...args) {
try {
return originalCSSSupports.apply(this, args);
} catch(e) {
return false;
}
};
} catch (e) {}
// -------------------------------------------------------------------------
// 30. Date/getTimezoneOffset (НОВЫЙ)
// -------------------------------------------------------------------------
try {
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
Object.defineProperty(Date.prototype, 'getTimezoneOffset', {
value: function() {
// Moscow timezone is UTC+3, so offset is -180 minutes
return -180;
},
configurable: true,
});
} catch (e) {}
if (originalPrepareStackTrace) {
return originalPrepareStackTrace(error, filteredStack);
// -------------------------------------------------------------------------
// 31. Screen orientation API (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (screen.orientation) {
Object.defineProperty(screen.orientation, 'type', { get: () => 'landscape-primary', configurable: true });
Object.defineProperty(screen.orientation, 'angle', { get: () => 0, configurable: true });
const originalLock = screen.orientation.lock.bind(screen.orientation);
screen.orientation.lock = function(type) {
return Promise.resolve();
};
screen.orientation.unlock = function() {};
}
} catch (e) {}
// -------------------------------------------------------------------------
// 32. VisualViewport (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (window.visualViewport) {
Object.defineProperty(window.visualViewport, 'width', { get: () => 1920, configurable: true });
Object.defineProperty(window.visualViewport, 'height', { get: () => 1080 - 100, configurable: true });
Object.defineProperty(window.visualViewport, 'scale', { get: () => 1, configurable: true });
Object.defineProperty(window.visualViewport, 'offsetLeft', { get: () => 0, configurable: true });
Object.defineProperty(window.visualViewport, 'offsetTop', { get: () => 0, configurable: true });
}
} catch (e) {}
// -------------------------------------------------------------------------
// 33. NavigatorUAData (НОВЫЙ) - дополнительная защита
// -------------------------------------------------------------------------
try {
if (Navigator.prototype.userAgentData) {
// Убеждаемся что все свойства присутствуют
}
} catch (e) {}
// -------------------------------------------------------------------------
// 34. Connection API (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (navigator.connection && navigator.connection.saveData !== undefined) {
Object.defineProperty(navigator.connection, 'saveData', { get: () => false, configurable: true });
Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g', configurable: true });
Object.defineProperty(navigator.connection, 'rtt', { get: () => 50, configurable: true });
Object.defineProperty(navigator.connection, 'downlink', { get: () => 10, configurable: true });
}
} catch (e) {}
// -------------------------------------------------------------------------
// 35. IndexedDB - убеждаемся что работает (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// Проверяем что IndexedDB доступен
const db = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
if (db) {
// IndexedDB работает - это хорошо
}
} catch (e) {}
// -------------------------------------------------------------------------
// 36. localStorage/sessionStorage (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// Проверяем что storage работает
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
} catch(e) {
// Storage может быть заблокирован в некоторых режимах
}
} catch (e) {}
// -------------------------------------------------------------------------
// 37. Document.cookie (НОВЫЙ)
// -------------------------------------------------------------------------
try {
const originalCookieDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
if (originalCookieDescriptor) {
// Cookie работают - это хорошо
}
} catch (e) {}
// -------------------------------------------------------------------------
// 38. Math.random() - добавляем trace (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// Перехватываем Math.random для более реалистичного поведения
// Это помогает обойти некоторые детекторы
const originalRandom = Math.random;
let callCount = 0;
Math.random = function() {
callCount++;
const result = originalRandom();
return result;
};
} catch (e) {}
// -------------------------------------------------------------------------
// 39. Event listeners - добавляем реалистичные listeners (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// Добавляем слушатели для常见 events если их нет
if (!window.onLine) {
window.addEventListener = window.addEventListener || function() {};
window.removeEventListener = window.removeEventListener || function() {};
}
return filteredStack.map(frame => ` at ${frame}`).join('\n');
// Navigator.onLine
Object.defineProperty(navigator, 'onLine', { get: () => true, configurable: true });
} catch (e) {}
// -------------------------------------------------------------------------
// 40. WebRTC - полная эмуляция (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection) {
// Создаем поддельные RTCPeerConnection
const originalRTC = window.RTCPeerConnection;
if (originalRTC) {
window.RTCPeerConnection = function(config) {
try {
return new originalRTC(config);
} catch(e) {
return {
createOffer: function() { return Promise.resolve({}); },
createAnswer: function() { return Promise.resolve({}); },
setLocalDescription: function() { return Promise.resolve(); },
setRemoteDescription: function() { return Promise.resolve(); },
close: function() {},
onicecandidate: null,
ontrack: null,
};
}
};
}
}
} catch (e) {}
// -------------------------------------------------------------------------
// 41. console.log - stealth mode (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// Перехватываем console чтобы скрыть наш stealth script
const originalConsoleLog = console.log;
console.log = function(...args) {
// Фильтруем наши сообщения
if (args[0] && typeof args[0] === 'string' && args[0].includes('[STEALTH]')) {
return;
}
return originalConsoleLog.apply(this, args);
};
} catch (e) {}
// -------------------------------------------------------------------------
// 42. Function.prototype.toString - spoofing (НОВЫЙ)
// -------------------------------------------------------------------------
try {
const nativeToString = Function.prototype.toString;
const spoofedFunctions = new Map();
Function.prototype.toString = function() {
if (spoofedFunctions.has(this)) {
return spoofedFunctions.get(this);
}
return nativeToString.call(this);
};
spoofedFunctions.set(Function.prototype.toString, 'function toString() { [native code] }');
if (window.Permissions && window.Permissions.prototype.query) {
spoofedFunctions.set(window.Permissions.prototype.query, 'function query() { [native code] }');
}
} catch (e) {}
// -------------------------------------------------------------------------
// 43. Добавляем "реальные" события (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// Имитируем реальное использование браузера
setTimeout(function() {
try {
// Добавляем слушатели для scroll, mouse movement
let lastScroll = 0;
window.addEventListener('scroll', function() {
lastScroll = window.scrollY;
}, { passive: true });
// Имитируем периодическое движение мыши
setInterval(function() {
try {
if (Math.random() < 0.1) {
// Случайное движение мыши
const event = new MouseEvent('mousemove', {
bubbles: true,
cancelable: true,
view: window,
});
// Не отправляем - просто имитируем активность
}
} catch(e) {}
}, 5000);
} catch(e) {}
}, Math.random() * 2000 + 1000); // Рандомная задержка
} catch (e) {}
// -------------------------------------------------------------------------
// 44. Service Workers (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (navigator.serviceWorker) {
// Service workers должны работать
}
} catch (e) {}
// -------------------------------------------------------------------------
// 45. Push API (НОВЫЙ)
// -------------------------------------------------------------------------
try {
if (PushManager) {
PushManager.prototype.subscribe = function(options) {
return Promise.resolve({
endpoint: '',
keys: { p256dh: '', auth: '' },
getKey: function() { return null; },
toJSON: function() { return { endpoint: '', keys: this.keys }; },
});
};
PushManager.prototype.getSubscription = function() {
return Promise.resolve(null);
};
}
} catch (e) {}
// -------------------------------------------------------------------------
// 46. WebSocket (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// WebSocket должен работать нормально
} catch (e) {}
// -------------------------------------------------------------------------
// 47. Element.getBoundingClientRect - добавляем noise (НОВЫЙ)
// -------------------------------------------------------------------------
try {
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
Element.prototype.getBoundingClientRect = function() {
const rect = originalGetBoundingClientRect.call(this);
// Добавляем микроскопический шум
const noise = 0.1;
return {
x: rect.x + (Math.random() - 0.5) * noise,
y: rect.y + (Math.random() - 0.5) * noise,
width: rect.width,
height: rect.height,
top: rect.top + (Math.random() - 0.5) * noise,
right: rect.right + (Math.random() - 0.5) * noise,
bottom: rect.bottom + (Math.random() - 0.5) * noise,
left: rect.left + (Math.random() - 0.5) * noise,
};
};
} catch (e) {}
// -------------------------------------------------------------------------
// 48. window.frameElement (НОВЫЙ)
// -------------------------------------------------------------------------
try {
Object.defineProperty(window, 'frameElement', {
get: function() { return null; },
configurable: true
});
} catch (e) {}
// -------------------------------------------------------------------------
// 49. window.length (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// window.length должен быть равен количеству фреймов
if (window.length === undefined) {
Object.defineProperty(window, 'length', {
get: function() { return window.frames.length; },
configurable: true
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 50. Install Trigger (для old school detection) (НОВЫЙ)
// -------------------------------------------------------------------------
try {
window.InstallTrigger = {
install: function() { return { status: 'installed' }; },
updateCheck: function() { return {} },
enabled: function() { return false; },
};
} catch (e) {}
// -------------------------------------------------------------------------
// 51. Отслеживание и предотвращение detection (НОВЫЙ)
// -------------------------------------------------------------------------
try {
// MutationObserver - должен работать
if (window.MutationObserver) {
// MutationObserver работает
}
// IntersectionObserver
if (window.IntersectionObserver) {
// IntersectionObserver работает
}
// ResizeObserver
if (window.ResizeObserver) {
// ResizeObserver работает
}
} catch (e) {}
// -------------------------------------------------------------------------
// Готово — все патчи применены
// -------------------------------------------------------------------------
console.log('[STEALTH] Maximum stealth mode initialized');
})();