Metadata-Version: 2.4
Name: rtfox-browser
Version: 0.0.2
Summary: Undetected Chrome browser driver with proxy support and captcha solving.
Home-page: https://github.com/rtf-labs-studio/rtfox-browser
Author: rtf-labs-studio
Author-email: rtf.labs.studio@gmail.com
License: GPL-3.0
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: 2captcha-python
Requires-Dist: loguru>=0.7.3
Requires-Dist: requests>=2.33.1
Requires-Dist: selenium>=4.43.0
Requires-Dist: SocksiPy-branch>=1.01
Requires-Dist: websockets>=16.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# rtfox-browser

> Undetected Chrome browser driver with SOCKS5 proxy, captcha solving and multiprocessing support.

![Cloudflare bypass](docs/gif_captcha_solver_2.gif)

---

## Based on

This project is a fork of [undetected-chromedriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver) by [@UltrafunkAmsterdam](https://github.com/ultrafunkamsterdam).

We took the core Chrome driver stealth patching logic as a base and extended it with:

- SOCKS5 proxy support with authentication
- Built-in captcha solving module with plugin API
- Multiprocessing isolation (per-worker profiles and binaries)
- On-the-fly proxy replacement without browser restart
- Structured logging via loguru
- Improved cleanup and shutdown handling

Original project is licensed under GPL-3.0 — this fork inherits the same license.

---

## Features

- 🛡️ Bypasses CloudFlare / hCaptcha / Imperva detection out of the box
- 🔒 SOCKS5 / HTTP / HTTPS proxy support with authentication
- 🔄 On-the-fly proxy replacement via `proxy_replacement()` — no restart needed
- 🧩 Built-in captcha solving module (2 solvers included)
- ⚙️ Custom captcha solver API — share your own solvers
- 🚀 Multiprocessing support
- 🖥️ Cross-platform: Linux, macOS, Windows

---

## Installation

```bash
pip install rtfox-browser
```

---

## Quick start

```python
import rtfox_browser as uc

driver = uc.Chrome()
driver.get("https://example.com")
driver.quit()
```

---

## Proxy

### SOCKS5 with authentication

```python
driver = uc.Chrome(
    proxy={
        "host": "1.2.3.4",
        "port": 1080,
        "type": "socks5",
        "user": "username",
        "pass": "password",
    }
)
```

### HTTP proxy (no auth)

```python
driver = uc.Chrome(
    proxy={
        "host": "1.2.3.4",
        "port": 8080,
        "type": "http",
    }
)
```

### Named keyword arguments (alternative style)

```python
from rtfox_browser.proxy_types import ProxyType

driver = uc.Chrome(
    proxy_host="1.2.3.4",
    proxy_port=1080,
    proxy_type=ProxyType.SOCKS5,
    proxy_user="username",
    proxy_pass="password",
)
```

### Proxy exceptions

| Exception | Cause |
|---|---|
| `ProxyError` | Base class for all proxy errors |
| `ProxyConnectionError` | Failed to connect to proxy (invalid host/port) |
| `ProxyAuthError` | Wrong login or password |
| `ProxyTimeoutError` | Proxy does not respond within timeout |
| `ProxyInvalidAddressError` | Malformed host or port format |
| `ProxyConfigError` | Missing or invalid proxy configuration fields |

```python
from rtfox_browser.exceptions import (
    ProxyError,
    ProxyConnectionError,
    ProxyAuthError,
    ProxyTimeoutError,
    ProxyInvalidAddressError,
    ProxyConfigError,
)

try:
    driver = uc.Chrome(proxy={
        "host": "1.2.3.4",
        "port": 1080,
        "type": "socks5",
        "user": "username",
        "pass": "password",
    })
except ProxyConfigError:
    print("Invalid or incomplete proxy config")
except ProxyInvalidAddressError:
    print("Malformed host or port")
except ProxyAuthError:
    print("Wrong credentials")
except ProxyTimeoutError:
    print("Proxy timed out")
except ProxyConnectionError:
    print("Could not connect to proxy")
except ProxyError:
    print("General proxy failure")
```

---

## On-the-fly proxy replacement

Replace the active proxy at any time without restarting Chrome.
A local transparent tunnel is always running, so switching works even on sessions started without a proxy.

```python
driver = uc.Chrome()

# Switch to a new proxy
driver.proxy_replacement({
    "host": "5.6.7.8",
    "port": 1080,
    "type": "socks5",
    "user": "alice",
    "pass": "secret",
})

# Switch to direct connection (no proxy)
driver.proxy_replacement(None)

# Named-argument style
driver.proxy_replacement(
    host="5.6.7.8",
    port=1080,
    proxy_type="socks5",
    user="alice",
    password="secret",
)
```

By default `proxy_replacement()` clears browser cache and cookies after switching so Chrome opens fresh connections through the new proxy. Pass `clear_connections=False` to skip this step.

---

## Multiprocessing

![Multiprocessing](docs/captcha_solver_pros.gif)

Each worker gets its own isolated Chrome profile and chromedriver binary.

`worker_id` is optional — if omitted, a random UUID is assigned automatically. Pass it explicitly only if you want predictable names in logs or file paths.

```python
from multiprocessing import Pool
import rtfox_browser as uc

def run_worker(worker_id):
    driver = uc.Chrome(worker_id=worker_id)
    driver.get("https://example.com")
    driver.quit()

with Pool(4) as pool:
    pool.map(run_worker, ["w1", "w2", "w3", "w4"])
```

Without explicit `worker_id`:

```python
def run_worker(_):
    driver = uc.Chrome()  # worker_id auto-assigned as UUID
    driver.get("https://example.com")
    driver.quit()

with Pool(4) as pool:
    pool.map(run_worker, range(4))
```

---

## Captcha solving

rtfox-browser includes a captcha module with 2 built-in solvers.
Solvers are loaded automatically from a `solvers/` directory in your working directory.

### Setup

```python
from rtfox_browser.captcha import CaptchaService

driver = uc.Chrome()
captcha = CaptchaService(api_key="YOUR_2CAPTCHA_KEY", driver=driver)

# Check available solvers
print(captcha.available())
# ['ebay_hcaptcha', 'aws_image']
```

### Solve a captcha

```python
driver.get("https://example.com")
captcha.ebay_hcaptcha()
```

### Custom solvers directory

```python
captcha = CaptchaService(
    api_key="YOUR_KEY",
    driver=driver,
    solvers_dir="/path/to/your/solvers",
)
```

---

## Creating your own captcha solver

Drop a file into the `solvers/` folder — it will be picked up automatically. No registration needed.

**1. Create `solvers/my_solver.py`:**

```python
from rtfox_browser.captcha import BaseCaptchaSolver

class MySolver(BaseCaptchaSolver):
    name = "my_captcha"  # becomes the method name on CaptchaService

    def solve(self, **kwargs) -> bool:
        driver = self.driver      # Selenium WebDriver
        api_key = self.api_key    # 2captcha API key

        # your solving logic here
        ...
        return True  # True = solved, False = failed
```

**2. Use it:**

```python
captcha = CaptchaService(api_key="KEY", driver=driver)
captcha.my_captcha()
```

**3. Or register manually at runtime:**

```python
captcha.register(MySolver)
captcha.my_captcha()
```

### BaseCaptchaSolver API

| Property | Type | Description |
|---|---|---|
| `name` | `str` | Method name exposed on `CaptchaService` |
| `self.driver` | `WebDriver` | The active browser instance |
| `self.api_key` | `str` | API key passed to `CaptchaService` |
| `solve(**kwargs)` | `bool` | Your solving logic, return `True` on success |

---

## ProxyConfig reference

You can construct proxy config directly using `ProxyConfig`:

```python
from rtfox_browser.proxy_types import ProxyConfig, ProxyType

# From a dict
config = ProxyConfig.from_dict({
    "host": "1.2.3.4",
    "port": 8080,
    "type": "socks5",
    "user": "alice",
    "pass": "secret",
})

# From named arguments
config = ProxyConfig.from_args(
    host="1.2.3.4",
    port=8080,
    proxy_type=ProxyType.SOCKS5,
    user="alice",
    password="secret",
)

# Pass directly to Chrome
driver = uc.Chrome(proxy=config)

# Or use for proxy_replacement
driver.proxy_replacement(config)
```

Supported proxy types: `socks5`, `http`, `https`.

---

## Cross-platform support

| OS | Supported |
|---|---|
| Linux | ✅ |
| macOS | ✅ |
| Windows | ✅ |

---

## Community

💬 Telegram: [@rtf_labs_studio](https://t.me/rtf_labs_studio)
🎥 YouTube: [RTF Labs Studio](https://www.youtube.com/@RTF_Labs_Studio)

---

## License

GPL-3.0 © [rtf-labs-studio](https://github.com/rtf-labs-studio)
