autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
333
skills/anti-detection-browser/SKILL.md
Normal file
333
skills/anti-detection-browser/SKILL.md
Normal file
@@ -0,0 +1,333 @@
|
||||
---
|
||||
name: anti-detection-browser
|
||||
description: Browser automation with Cloudflare Turnstile & CDP-detection bypass. DrissionPage + Turnstile patch extension + proxy auth forwarder. Use when headless browsers are blocked by Cloudflare or anti-bot detection.
|
||||
category: automation
|
||||
---
|
||||
|
||||
# Anti-Detection Browser Automation
|
||||
|
||||
Обход Cloudflare Turnstile и CDP-детекции при автоматизации браузера.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- Cloudflare блокирует headless-браузеры (Playwright, Puppeteer, Selenium)
|
||||
- Страница показывает "Attention Required! | Cloudflare" или "Sorry, you have been blocked"
|
||||
- `MouseEvent.screenX/screenY = 0` детектится как CDP-браузер
|
||||
- Нужна регистрация/логин на сайтах с Turnstile (x.ai, Clerk, TokenHarbor)
|
||||
|
||||
## Почему Playwright не работает
|
||||
|
||||
Playwright использует CDP (Chrome DevTools Protocol). Cloudflare Turnstile детектит CDP-браузеры через:
|
||||
- `MouseEvent.screenX/screenY = 0` (в headless-режиме координаты мыши не эмулируются)
|
||||
- `navigator.webdriver = true`
|
||||
- Отсутствие реальных движений мыши перед кликом
|
||||
|
||||
## Решение: DrissionPage + Turnstile Patch
|
||||
|
||||
**DrissionPage** — библиотека управления Chromium, которая не использует WebDriver и не выставляет `navigator.webdriver`.
|
||||
|
||||
**Turnstile patch** — Chrome extension (Manifest V3), который инжектит скрипт в `MAIN` world на `document_start`, переопределяя `MouseEvent.prototype.screenX/Y` случайными реалистичными значениями.
|
||||
|
||||
### Структура расширения
|
||||
|
||||
```
|
||||
turnstilePatch/
|
||||
├── manifest.json
|
||||
└── script.js
|
||||
```
|
||||
|
||||
**manifest.json**:
|
||||
```json
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Turnstile Patch",
|
||||
"version": "1.0",
|
||||
"content_scripts": [{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["script.js"],
|
||||
"run_at": "document_start",
|
||||
"world": "MAIN"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**script.js**:
|
||||
```javascript
|
||||
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
||||
get: function() { return Math.floor(Math.random() * 400) + 800; }
|
||||
});
|
||||
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
||||
get: function() { return Math.floor(Math.random() * 200) + 400; }
|
||||
});
|
||||
```
|
||||
|
||||
### Настройка DrissionPage
|
||||
|
||||
```python
|
||||
from DrissionPage import Chromium, ChromiumOptions
|
||||
|
||||
co = ChromiumOptions()
|
||||
co.auto_port()
|
||||
co.set_argument("--no-sandbox")
|
||||
co.set_argument("--disable-gpu")
|
||||
co.set_argument("--disable-dev-shm-usage")
|
||||
co.set_argument("--disable-software-rasterizer")
|
||||
|
||||
# Загрузка Turnstile patch
|
||||
EXTENSION_PATH = os.path.abspath("turnstilePatch")
|
||||
co.add_extension(EXTENSION_PATH)
|
||||
|
||||
# Прокси (если без аутентификации)
|
||||
co.set_proxy("http://proxy:port")
|
||||
|
||||
browser = Chromium(co)
|
||||
```
|
||||
|
||||
### Ожидание Turnstile
|
||||
|
||||
После навигации нужно ждать 15-30 секунд, пока Turnstile пройдёт в non-interactive режиме:
|
||||
|
||||
```python
|
||||
page.get("https://target.com/sign-up")
|
||||
run_logger.info("Waiting for Cloudflare Turnstile...")
|
||||
deadline = time.time() + 30
|
||||
while time.time() < deadline:
|
||||
has_challenge = page.run_js(
|
||||
"return document.title.includes('Attention Required') || "
|
||||
"document.querySelector('#challenge-running') !== null || "
|
||||
"document.body.innerText.includes('verifying you are human')"
|
||||
)
|
||||
if not has_challenge:
|
||||
break
|
||||
time.sleep(2)
|
||||
```
|
||||
|
||||
### Поиск кнопок с fallback
|
||||
|
||||
На странице может быть разный язык. Использовать JS-поиск по тексту:
|
||||
|
||||
```javascript
|
||||
const candidates = Array.from(document.querySelectorAll('button, a, [role="button"]'));
|
||||
const target = candidates.find(node => {
|
||||
const text = (node.innerText || node.textContent || '').replace(/\s+/g, '').toLowerCase();
|
||||
return text.includes('signupwithemail') ||
|
||||
text.includes('continue with email') ||
|
||||
text.includes('sign up with email') ||
|
||||
text.includes('use email') ||
|
||||
text.includes('email');
|
||||
});
|
||||
if (target) target.click();
|
||||
```
|
||||
|
||||
## Proxy Auth Forwarder
|
||||
|
||||
DrissionPage и nodriver НЕ поддерживают прокси с аутентификацией. Ошибка: "你似乎在设置使用账号密码的代理,暂时不支持这种代理".
|
||||
|
||||
Решение: локальный forward proxy, который добавляет `Proxy-Authorization` header.
|
||||
|
||||
### ⚠️ HTTP-based forwarder НЕ работает с Chrome
|
||||
|
||||
`http.server.HTTPServer` + `BaseHTTPRequestHandler` ломает CONNECT-туннели для Chrome — браузер получает `chrome-error://chromewebdata/`. Curl работает, Chrome — нет.
|
||||
|
||||
**Использовать RAW TCP forwarder** (см. `scripts/tcp_forwarder.py`):
|
||||
|
||||
```python
|
||||
import socket, threading, select, base64
|
||||
|
||||
UPSTREAM_HOST = 'residential-x.bpproxy.at'
|
||||
UPSTREAM_PORT = 1000
|
||||
AUTH = base64.b64encode(b'user:pass').decode()
|
||||
LOCAL_PORT = 9129
|
||||
|
||||
def handle_client(client_sock):
|
||||
data = b''
|
||||
while b'\r\n\r\n' not in data:
|
||||
chunk = client_sock.recv(4096)
|
||||
if not chunk: return
|
||||
data += chunk
|
||||
|
||||
request = data.decode('latin-1', errors='replace')
|
||||
first_line = request.split('\r\n')[0]
|
||||
method, target, _ = first_line.split(' ', 2)
|
||||
|
||||
if method == 'CONNECT':
|
||||
host, port_str = target.rsplit(':', 1)
|
||||
port = int(port_str)
|
||||
remote = socket.create_connection((UPSTREAM_HOST, UPSTREAM_PORT), timeout=10)
|
||||
connect_req = f'CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\nProxy-Authorization: Basic {AUTH}\r\n\r\n'
|
||||
remote.send(connect_req.encode())
|
||||
resp = b''
|
||||
while b'\r\n\r\n' not in resp:
|
||||
resp += remote.recv(4096)
|
||||
if b'200' not in resp.split(b'\r\n')[0]:
|
||||
client_sock.send(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||
remote.close()
|
||||
return
|
||||
client_sock.send(b'HTTP/1.1 200 Connection Established\r\n\r\n')
|
||||
client_sock.settimeout(30)
|
||||
remote.settimeout(30)
|
||||
socks = [client_sock, remote]
|
||||
while True:
|
||||
r, _, _ = select.select(socks, [], [], 30)
|
||||
if not r: break
|
||||
for s in r:
|
||||
d = s.recv(8192)
|
||||
if not d: return
|
||||
dst = remote if s is client_sock else client_sock
|
||||
dst.sendall(d)
|
||||
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind(('127.0.0.1', LOCAL_PORT))
|
||||
server.listen(50)
|
||||
while True:
|
||||
client, addr = server.accept()
|
||||
threading.Thread(target=handle_client, args=(client,), daemon=True).start()
|
||||
```
|
||||
|
||||
Запуск:
|
||||
```bash
|
||||
python tcp_forwarder.py &
|
||||
# Затем использовать http://127.0.0.1:9129 как browser_proxy
|
||||
```
|
||||
|
||||
## Geo-Blocking
|
||||
|
||||
Сервисы типа x.ai гео-блокируют по стране выхода прокси. Проверять перед использованием:
|
||||
|
||||
```bash
|
||||
curl -x "http://user:pass@proxy:port" -s http://ip-api.com/json
|
||||
# Проверить поле "country" — x.ai доступен из US, CA, UK, EU, но НЕ из IQ, RU, CN
|
||||
```
|
||||
|
||||
## Nodriver: Fallback When DrissionPage Fails
|
||||
|
||||
DrissionPage может быть детектирован Cloudflare даже с Turnstile patch. Симптом: `PageDisconnectedError: 与页面的连接已断开` — Cloudflare активно рвёт CDP-соединение.
|
||||
|
||||
**Nodriver** (`pip install nodriver`) — альтернатива с встроенной антидетект-защитой. Не требует отдельного Turnstile patch.
|
||||
|
||||
### Настройка nodriver
|
||||
|
||||
```python
|
||||
import nodriver as uc
|
||||
|
||||
browser = await uc.start(
|
||||
headless=False,
|
||||
browser_args=[
|
||||
"--window-size=1280,900",
|
||||
"--no-first-run",
|
||||
"--no-default-browser-check",
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
"--proxy-server=http://127.0.0.1:9129", # через proxy forwarder
|
||||
"--ignore-certificate-errors",
|
||||
],
|
||||
)
|
||||
tab = await browser.get("https://accounts.x.ai/sign-up?redirect=grok-com")
|
||||
```
|
||||
|
||||
### API ключевые отличия от DrissionPage
|
||||
|
||||
- **Async**: все методы `await`
|
||||
- `tab.find(text)` — поиск элемента по тексту (аналог `page.run_js(...find text...)`)
|
||||
- `tab.select(selector)` — CSS-селектор
|
||||
- `tab.evaluate(js)` — выполнить JS
|
||||
- `el.click()`, `el.send_keys(text)` — взаимодействие
|
||||
- Обернуть в `asyncio.wait_for()` для таймаутов
|
||||
|
||||
### Google OAuth Bypass для accounts.x.ai
|
||||
|
||||
**Проблема**: `accounts.x.ai` блокирует ВСЕ bpproxy residential IP (403) напрямую, но Google OAuth (`accounts.google.com`) принимает те же прокси (302).
|
||||
|
||||
**Решение**: использовать Google OAuth flow вместо прямой email-регистрации:
|
||||
|
||||
1. Зайти на `accounts.x.ai/sign-up?redirect=grok-com`
|
||||
2. Нажать «Continue with Google» (не «Use email»)
|
||||
3. Google OAuth-страница загружается через прокси (Google принимает bpproxy)
|
||||
4. Ввести Gmail + пароль + TOTP (если 2FA)
|
||||
5. Пройти consent screen
|
||||
6. Редирект обратно на Grok
|
||||
|
||||
**Требования**: Gmail аккаунты с 2FA-секретами (формат как в `tusk_gmail_accounts.json`).
|
||||
|
||||
```python
|
||||
# Пример: поиск кнопки Google
|
||||
clicked = await click_text(tab, [
|
||||
"Continue with Google", "Sign in with Google",
|
||||
"Sign up with Google", "Google",
|
||||
], timeout=10)
|
||||
```
|
||||
|
||||
### Port Conflicts: Multiple Proxy Forwarders
|
||||
|
||||
Никогда не запускать несколько экземпляров proxy forwarder на одном порту — они конфликтуют и порт становится недоступным. При смене upstream-прокси:
|
||||
|
||||
1. Убить старый forwarder: `taskkill /F /PID <pid>`
|
||||
2. Проверить что порт свободен: `netstat -ano | grep <port>`
|
||||
3. Запустить новый forwarder на **уникальном** порту
|
||||
|
||||
**Рекомендация**: использовать разные порты для разных upstream-прокси (9128, 9129, 9130...).
|
||||
|
||||
### Nodriver `browser.stop()` не awaitable
|
||||
|
||||
`browser.stop()` в nodriver возвращает `None` — не использовать `await`:
|
||||
|
||||
```python
|
||||
# ❌ ОШИБКА: TypeError: object NoneType can't be used in 'await' expression
|
||||
await browser.stop()
|
||||
|
||||
# ✅ ПРАВИЛЬНО
|
||||
browser.stop()
|
||||
```
|
||||
|
||||
### TCP Forwarder: всегда вместо HTTP-based
|
||||
|
||||
HTTP-based forwarder (`http.server.HTTPServer`) не работает с Chrome — CONNECT-туннели ломаются. Всегда использовать RAW TCP forwarder (`scripts/tcp_forwarder.py`).
|
||||
|
||||
## Pitfalls
|
||||
|
||||
### Playwright Headless Blocked by Cloudflare (General)
|
||||
Playwright's headless Chromium is detected by Cloudflare on many sites, not just those with Turnstile. Symptoms: `page.goto()` times out, empty page content, or Cloudflare challenge page. Use `headless=False` with a persistent Chrome user profile (`launch_persistent_context`), or connect via CDP to an existing Chrome instance (`connect_over_cdp`).
|
||||
|
||||
### Playwright CDP Works, Headless Doesn't
|
||||
`connect_over_cdp("http://127.0.0.1:9222")` to an existing Chrome with remote debugging works reliably because it uses the real Chrome profile. Headless Playwright (`launch()`) is detected. For automation that needs Cloudflare bypass, always prefer CDP connection over headless launch.
|
||||
|
||||
### Nodriver Windows Asyncio Crashes (2026-08-21)
|
||||
Nodriver (`uc.start()`) crashes on Windows with `ValueError: I/O operation on closed pipe` in `asyncio.proactor_events`. This is a Windows-specific asyncio issue. Fall back to Playwright with `headless=False` or CDP connection.
|
||||
|
||||
### MCP Browser as Reliable Fallback
|
||||
When Playwright and nodriver both fail, the Chrome DevTools MCP (`mcp__chrome_devtools__*` tools) works reliably. It uses the existing Chrome instance with the user's profile, avoiding Cloudflare detection entirely. Use `evaluate_script` with nativeInputSetter for fastest form interactions.
|
||||
|
||||
### Standalone Chrome + CDP: самопроходящий Turnstile (2026-09-10)
|
||||
Когда все MCP/headless-пути висят на SPA-бандле (белая страница, бандл pending, хотя curl качает 200), а firecrawl_interact рвёт сессии — поднять отдельный Chrome с `--remote-debugging-port=9333 --user-data-dir=<fresh>` (background=true), подключиться Playwright `connect_over_cdp`, заполнить форму и **поллить скрытое поле `cf-turnstile-response` до length>10** — в реальном Chrome non-interactive Turnstile решается сам за 10-20 сек. Полный разбор (RainProxy): `references/rainproxy-cdp-turnstile.md` — там же pitfalls: `has-text("Login")` ловит "Login with Google", Gmail-алиасы с `+` отклоняются, UTF-7 имена папок Gmail при русской локали.
|
||||
|
||||
### accounts.x.ai blocks ALL bpproxy residential IPs (2026-08-14)
|
||||
|
||||
Все 25 bpproxy residential прокси возвращают `403` при прямом обращении к `accounts.x.ai`. Google (`accounts.google.com`) принимает те же прокси. Использовать Google OAuth flow вместо прямого email signup.
|
||||
|
||||
### DrissionPage CDP disconnection
|
||||
|
||||
Cloudflare может детектировать и разрывать CDP-соединение DrissionPage (`PageDisconnectedError`). Переключиться на nodriver.
|
||||
|
||||
### DrissionPage proxy auth
|
||||
DrissionPage не поддерживает `user:pass@host:port` в URL прокси. Использовать proxy forwarder.
|
||||
|
||||
### Turnstile timing
|
||||
Non-interactive Turnstile требует 5-30 секунд. Недостаточное ожидание → кнопки не найдены (страница ещё не загрузилась после challenge).
|
||||
|
||||
### Geo-blocking bpproxy
|
||||
bpproxy residential выходят из разных стран (Ирак, Канада, etc.). Всегда проверять `ip-api.com/json` перед использованием с гео-зависимыми сервисами.
|
||||
|
||||
### bpproxy hardsession истекает
|
||||
Hardsession-прокси могут умереть через несколько часов. Периодически перегенерировать пул через `bpproxy` skill.
|
||||
|
||||
## Связанные скиллы
|
||||
|
||||
- `bpproxy` — генерация и управление прокси
|
||||
- `bulk-account-registration` — пайплайн массовой регистрации
|
||||
|
||||
## Связанные файлы
|
||||
|
||||
- `references/grok-registration-pipeline.md` — конкретный пайплайн регистрации Grok (x.ai) с DrissionPage
|
||||
- `scripts/proxy_forwarder.py` — HTTP forward proxy (DEPRECATED — не работает с Chrome, используй tcp_forwarder.py)
|
||||
- `scripts/tcp_forwarder.py` — RAW TCP forward proxy (работает с Chrome, nodriver, DrissionPage)
|
||||
- `templates/turnstile-patch/` — шаблон Chrome extension для обхода CDP-детекции (manifest.json + script.js)
|
||||
@@ -0,0 +1,50 @@
|
||||
# Grok (x.ai) Registration Pipeline
|
||||
|
||||
## Working Setup (2026-08-14)
|
||||
|
||||
**Location**: `C:\Users\User\Desktop\авторег проект\ready_grok_reg\`
|
||||
|
||||
### Components
|
||||
|
||||
| Component | File | Purpose |
|
||||
|---|---|---|
|
||||
| Main script | `DrissionPage_example.py` (1401 lines) | Full registration flow |
|
||||
| Email IMAP | `email_register_t_online.py` | t-online.de OTP retrieval |
|
||||
| Config | `config.json` | Proxy, DuckMail, API settings |
|
||||
| Turnstile patch | `turnstilePatch/` | Chrome extension for CDP bypass |
|
||||
| Email pool | `working_mails.txt` | 17,893 t-online.de accounts |
|
||||
| Proxy pool | `bpproxy_live.json` | 25 live residential proxies |
|
||||
|
||||
### Flow
|
||||
|
||||
1. Start Chromium via DrissionPage with Turnstile patch extension
|
||||
2. Navigate to `https://accounts.x.ai/sign-up?redirect=grok-com`
|
||||
3. Wait for Cloudflare Turnstile to auto-pass (15-30s)
|
||||
4. Click "Sign up with email" button
|
||||
5. Get email from `working_mails.txt` → fill email field → submit
|
||||
6. Read OTP via IMAP (`secureimap.t-online.de:993`)
|
||||
7. Enter OTP → fill profile (name, password) → submit
|
||||
8. Collect SSO token → save to `sso/` directory
|
||||
|
||||
### Key Fixes Applied (2026-08-14)
|
||||
|
||||
1. **DrissionPage proxy auth**: DrissionPage doesn't support `user:pass@host` proxy URLs. Use `proxy_forwarder.py` on localhost:9128.
|
||||
2. **Geo-blocking**: bpproxy residential exits from random countries. Verify with `ip-api.com` before use. Canada (CA) works for x.ai.
|
||||
3. **Turnstile wait**: increased from 5s to 30s with active challenge detection loop.
|
||||
4. **Button text**: added more English fallbacks (`continue with email`, `sign up with email`, `use email`).
|
||||
|
||||
### Current Issues
|
||||
|
||||
- proc_a67640977778 running with Canada proxy + Turnstile wait fix
|
||||
- Awaiting results
|
||||
|
||||
### Dependencies
|
||||
|
||||
```
|
||||
DrissionPage==[REDACTED-IP]b14
|
||||
curl_cffi>=0.7.0
|
||||
```
|
||||
|
||||
### Original Source
|
||||
|
||||
Fork of [kevinr229/grok-maintainer](https://github.com/kevinr229/grok-maintainer), adapted for t-online.de IMAP.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Nodriver + Google OAuth Grok Registration
|
||||
|
||||
Конкретный пайплайн регистрации Grok (x.ai) через Google OAuth, когда прямой email signup блокируется Cloudflare.
|
||||
|
||||
## Проблема
|
||||
|
||||
`accounts.x.ai` блокирует ВСЕ bpproxy residential прокси (403). Даже DrissionPage с Turnstile patch получает `PageDisconnectedError` — Cloudflare рвёт CDP-соединение.
|
||||
|
||||
## Решение
|
||||
|
||||
Google OAuth flow через nodriver. Google (`accounts.google.com`) принимает bpproxy (302), а OAuth-редирект имеет больше шансов пройти.
|
||||
|
||||
## Файлы
|
||||
|
||||
- `grok_nodriver.py` — nodriver + email signup (DrissionPage-совместимый API)
|
||||
- `grok_google_oauth.py` — nodriver + Google OAuth с Gmail аккаунтами
|
||||
- `proxy_forwarder_9129.py` — локальный HTTP forward proxy на порту 9129
|
||||
|
||||
## Требования
|
||||
|
||||
- `nodriver` (pip install nodriver)
|
||||
- `pyotp` для TOTP 2FA
|
||||
- Gmail аккаунты в формате `tusk_gmail_accounts.json`:
|
||||
```json
|
||||
[{"email": "...@gmail.com", "password": "...", "2fa_secret": "..."}]
|
||||
```
|
||||
- Локальный proxy forwarder с bpproxy residential (Google принимает bpproxy)
|
||||
|
||||
## Flow
|
||||
|
||||
1. Запустить proxy forwarder: `python proxy_forwarder_9129.py`
|
||||
2. `python grok_google_oauth.py N` (N = количество аккаунтов)
|
||||
3. Nodriver открывает Chrome → `accounts.x.ai/sign-up` → ждёт Turnstile (~30 сек)
|
||||
4. Ищет кнопку «Continue with Google» → кликает
|
||||
5. Google OAuth: email → пароль → TOTP (если 2FA) → consent
|
||||
6. Редирект на Grok → аккаунт создан
|
||||
7. Сохраняет SSO в `sso/`
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Proxy forwarder port conflicts**: не запускать несколько экземпляров на одном порту
|
||||
- **bpproxy hardsession expiry**: прокси могут умереть через несколько часов
|
||||
- **Google может запросить дополнительную верификацию**: phone, recovery email
|
||||
- **TOTP timing**: код валиден 30 секунд, точное время на хосте критично
|
||||
@@ -0,0 +1,21 @@
|
||||
# RainProxy dashboard — CDP + самопроходящий Turnstile (2026-09-10)
|
||||
|
||||
Подтверждённый рабочий паттерн для Vite-SPA с Cloudflare Turnstile, когда headless-инструменты висят.
|
||||
|
||||
## Симптом
|
||||
- dashboard.rainproxy.io — пустая белая страница в Playwright MCP, chrome-devtools MCP и browser-use: бандл `/assets/index.*.js` (~900KB) остаётся `pending` / `ERR_CONNECTION_RESET`, хотя `curl` качает тот же файл (200). Причина сетевая/браузерная, не сайтовая.
|
||||
- Firecrawl stealth рендерит страницу, но интеракт-сессии сгорают между вызовами ("Job not found"), лимит 2 concurrent jobs, MCP-таймаут 60s — многошаговые формы с ожиданием капчи через firecrawl_interact ненадёжны.
|
||||
|
||||
## Рабочее решение: реальный Chrome + CDP
|
||||
1. `chrome.exe --remote-debugging-port=9333 --user-data-dir=<fresh_dir> --no-first-run <url>` через terminal(background=true). Fresh profile обязателен.
|
||||
2. Playwright `connect_over_cdp("http://127.0.0.1:9333")` → `b.contexts[0]` → найти страницу по подстроке URL.
|
||||
3. Заполнить поля по id (`#first_name`, `#email`, `#password`, `pg.check('#agree')`).
|
||||
4. **Turnstile проходит САМ** в реальном Chrome — не кликать, просто поллить:
|
||||
`document.querySelector('[name=cf-turnstile-response]').value.length > 10` (каждые 2 сек, до 30 итераций; типично 10-20 сек).
|
||||
5. Клик Register → редирект на /verify-email?vfemail=<base64(email)>.
|
||||
|
||||
## Pitfalls
|
||||
- Селектор `button:has-text("Login")` может попасть на соседнюю "Login with Google" (содержит подстроку) → неожиданный редирект на accounts.google.com. Google OAuth требует РЕАЛЬНЫЙ пароль аккаунта; IMAP app-пароль не подходит.
|
||||
- Валидатор email отклоняет плюс-алиасы (`user+tag@gmail.com` → "Please enter a valid email").
|
||||
- Gmail UTF-7 имена папок при русской локали: Spam = `"[Gmail]/&BCEEPwQwBDw-"`, All Mail = `"[Gmail]/&BBIEQQRP- &BD8EPgRHBEIEMA-"`. IMAP SEARCH с OR-критериями → BAD 'Could not parse command'; использовать SEARCH ALL / SINCE + фильтр в Python.
|
||||
- Verify-письмо RainProxy на Gmail dot-алиас не пришло за ~3 мин (2 отправки). Для массовых регов использовать отдельный почтовый пул (t-online.de), не Gmail-алиасы.
|
||||
104
skills/anti-detection-browser/scripts/proxy_forwarder.py
Normal file
104
skills/anti-detection-browser/scripts/proxy_forwarder.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Local HTTP forward proxy that injects Proxy-Authorization for upstream auth.
|
||||
Use when DrissionPage/Playwright doesn't support authenticated proxies.
|
||||
|
||||
Usage:
|
||||
1. Edit UPSTREAM, AUTH_USER, AUTH_PASS below
|
||||
2. Run: python proxy_forwarder.py
|
||||
3. Set browser_proxy to http://127.0.0.1:9128
|
||||
"""
|
||||
|
||||
import http.server
|
||||
import urllib.request
|
||||
import base64
|
||||
import socket
|
||||
import select
|
||||
|
||||
UPSTREAM = 'residential-x.bpproxy.at:1000'
|
||||
AUTH_USER = b'bpuser-XXX'
|
||||
AUTH_PASS = b'YYYY'
|
||||
AUTH = base64.b64encode(AUTH_USER + b':' + AUTH_PASS).decode()
|
||||
LOCAL = 9128
|
||||
|
||||
|
||||
class Proxy(http.server.BaseHTTPRequestHandler):
|
||||
timeout = 30
|
||||
|
||||
def do_GET(self):
|
||||
self._forward('GET')
|
||||
|
||||
def do_POST(self):
|
||||
self._forward('POST')
|
||||
|
||||
def do_HEAD(self):
|
||||
self._forward('HEAD')
|
||||
|
||||
def do_PUT(self):
|
||||
self._forward('PUT')
|
||||
|
||||
def _forward(self, method):
|
||||
try:
|
||||
url = self.path
|
||||
req = urllib.request.Request(url, method=method)
|
||||
req.add_header('Proxy-Authorization', f'Basic {AUTH}')
|
||||
for h, v in self.headers.items():
|
||||
if h.lower() not in ('host', 'proxy-connection', 'proxy-authorization'):
|
||||
req.add_header(h, v)
|
||||
if method in ('POST', 'PUT'):
|
||||
body = self.rfile.read(int(self.headers.get('Content-Length', 0)))
|
||||
req.data = body
|
||||
req.set_proxy(UPSTREAM, 'http')
|
||||
resp = urllib.request.urlopen(req, timeout=30)
|
||||
self.send_response(resp.status)
|
||||
for h, v in resp.getheaders():
|
||||
self.send_header(h, v)
|
||||
self.end_headers()
|
||||
self.wfile.write(resp.read())
|
||||
except Exception as e:
|
||||
self.send_error(502, str(e))
|
||||
|
||||
def do_CONNECT(self):
|
||||
host, port = self.path.split(':')
|
||||
port = int(port)
|
||||
try:
|
||||
uphost, upport = UPSTREAM.split(':')
|
||||
remote = socket.create_connection((uphost, int(upport)), timeout=10)
|
||||
connect_req = (
|
||||
f'CONNECT {host}:{port} HTTP/1.1\r\n'
|
||||
f'Host: {host}:{port}\r\n'
|
||||
f'Proxy-Authorization: Basic {AUTH}\r\n\r\n'
|
||||
)
|
||||
remote.send(connect_req.encode())
|
||||
resp = b''
|
||||
while b'\r\n\r\n' not in resp:
|
||||
resp += remote.recv(4096)
|
||||
if b'200' not in resp.split(b'\r\n')[0]:
|
||||
self.send_error(502, 'CONNECT failed')
|
||||
remote.close()
|
||||
return
|
||||
self.send_response(200, 'Connection Established')
|
||||
self.end_headers()
|
||||
self.connection.settimeout(30)
|
||||
remote.settimeout(30)
|
||||
socks = [self.connection, remote]
|
||||
while True:
|
||||
r, _, _ = select.select(socks, [], [], 30)
|
||||
if not r:
|
||||
break
|
||||
for s in r:
|
||||
try:
|
||||
d = s.recv(8192)
|
||||
if not d:
|
||||
return
|
||||
dst = remote if s is self.connection else self.connection
|
||||
dst.sendall(d)
|
||||
except Exception:
|
||||
return
|
||||
except Exception as e:
|
||||
self.send_error(502, str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'[*] Proxy Forwarder: 127.0.0.1:{LOCAL} -> {UPSTREAM}', flush=True)
|
||||
httpd = http.server.HTTPServer(('127.0.0.1', LOCAL), Proxy)
|
||||
httpd.serve_forever()
|
||||
115
skills/anti-detection-browser/scripts/tcp_forwarder.py
Normal file
115
skills/anti-detection-browser/scripts/tcp_forwarder.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import socket, threading, select, base64, json, re, sys
|
||||
"""TCP proxy forwarder for authenticated upstream proxies.
|
||||
|
||||
Usage: python tcp_forwarder.py [proxy_json_path] [local_port]
|
||||
- proxy_json_path: JSON file with proxy pool (default: C:/Users/User/tmp/bpproxy_live.json)
|
||||
- local_port: local port to listen on (default: 9129)
|
||||
|
||||
The script picks the FIRST proxy from the pool. Edit UPSTREAM_HOST/UPSTREAM_PORT/AUTH
|
||||
directly if you want a specific proxy.
|
||||
|
||||
Why TCP instead of HTTP-based:
|
||||
http.server.HTTPServer + BaseHTTPRequestHandler breaks CONNECT tunnels for Chrome.
|
||||
Chrome gets chrome-error://chromewebdata/ while curl works fine.
|
||||
Raw TCP relay handles CONNECT properly.
|
||||
"""
|
||||
|
||||
# --- CONFIG ---
|
||||
DEFAULT_PROXY_POOL = r"C:\Users\User\tmp\bpproxy_live.json"
|
||||
LOCAL_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 9129
|
||||
|
||||
# Load proxy from pool
|
||||
pool_path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_PROXY_POOL
|
||||
pool = json.loads(open(pool_path).read())
|
||||
proxy = pool[0]['url'] if isinstance(pool[0], dict) else pool[0]
|
||||
match = re.match(r'http://([^:]+):([^@]+)@([^:]+):(\d+)', proxy)
|
||||
user, password, host, port = match.groups()
|
||||
|
||||
UPSTREAM_HOST = host
|
||||
UPSTREAM_PORT = int(port)
|
||||
AUTH = base64.b64encode(f'{user}:{password}'.encode()).decode()
|
||||
|
||||
|
||||
def handle_client(client_sock):
|
||||
try:
|
||||
data = b''
|
||||
while b'\r\n\r\n' not in data:
|
||||
chunk = client_sock.recv(4096)
|
||||
if not chunk:
|
||||
return
|
||||
data += chunk
|
||||
|
||||
request = data.decode('latin-1', errors='replace')
|
||||
first_line = request.split('\r\n')[0]
|
||||
method, target, _ = first_line.split(' ', 2)
|
||||
|
||||
if method == 'CONNECT':
|
||||
host, port_str = target.rsplit(':', 1)
|
||||
port = int(port_str)
|
||||
|
||||
remote = socket.create_connection((UPSTREAM_HOST, UPSTREAM_PORT), timeout=10)
|
||||
connect_req = (
|
||||
f'CONNECT {host}:{port} HTTP/1.1\r\n'
|
||||
f'Host: {host}:{port}\r\n'
|
||||
f'Proxy-Authorization: Basic {AUTH}\r\n\r\n'
|
||||
)
|
||||
remote.send(connect_req.encode())
|
||||
|
||||
resp = b''
|
||||
while b'\r\n\r\n' not in resp:
|
||||
resp += remote.recv(4096)
|
||||
|
||||
if b'200' not in resp.split(b'\r\n')[0]:
|
||||
client_sock.send(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||
remote.close()
|
||||
return
|
||||
|
||||
client_sock.send(b'HTTP/1.1 200 Connection Established\r\n\r\n')
|
||||
_relay(client_sock, remote)
|
||||
else:
|
||||
remote = socket.create_connection((UPSTREAM_HOST, UPSTREAM_PORT), timeout=10)
|
||||
remote.send(data)
|
||||
_relay(client_sock, remote)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
client_sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _relay(a, b):
|
||||
a.settimeout(30)
|
||||
b.settimeout(30)
|
||||
socks = [a, b]
|
||||
while True:
|
||||
r, _, _ = select.select(socks, [], [], 30)
|
||||
if not r:
|
||||
break
|
||||
for s in r:
|
||||
try:
|
||||
d = s.recv(8192)
|
||||
if not d:
|
||||
return
|
||||
dst = b if s is a else a
|
||||
dst.sendall(d)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def main():
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind(('127.0.0.1', LOCAL_PORT))
|
||||
server.listen(50)
|
||||
print(f'[*] TCP Proxy: 127.0.0.1:{LOCAL_PORT} -> {UPSTREAM_HOST}:{UPSTREAM_PORT}', flush=True)
|
||||
|
||||
while True:
|
||||
client, addr = server.accept()
|
||||
t = threading.Thread(target=handle_client, args=(client,), daemon=True)
|
||||
t.start()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Turnstile Patch",
|
||||
"version": "1.0",
|
||||
"description": "Overrides MouseEvent.screenX/Y to bypass CDP-based bot detection",
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["script.js"],
|
||||
"run_at": "document_start",
|
||||
"world": "MAIN"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Override MouseEvent screenX/screenY to bypass CDP-based bot detection.
|
||||
// Cloudflare Turnstile detects CDP browsers by checking for screenX=0, screenY=0
|
||||
// which happens because CDP doesn't emulate real mouse coordinates.
|
||||
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
||||
get: function () {
|
||||
return Math.floor(Math.random() * 400) + 800;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
||||
get: function () {
|
||||
return Math.floor(Math.random() * 200) + 400;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user