Metadata-Version: 2.4
Name: selenium-proxy-tools
Version: 0.2.0
Summary: Authenticated & rotating proxies for Chrome-based browser automation (Selenium, SeleniumBase, undetected-chromedriver, Playwright). Handles authenticated SOCKS5/SOCKS4 and HTTP/HTTPS proxies and proxy pools via a local relay.
Project-URL: Homepage, https://github.com/SAKMZ/selenium-proxy-tools
Project-URL: Repository, https://github.com/SAKMZ/selenium-proxy-tools
Project-URL: Issues, https://github.com/SAKMZ/selenium-proxy-tools/issues
Author: Saif Ali (SAKMZ)
License-Expression: MIT
License-File: LICENSE
Keywords: authenticated-proxy,automation,chrome,playwright,proxy,proxy-pool,rotating-proxy,selenium,seleniumbase,socks4,socks5,undetected-chromedriver,webdriver
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
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
Classifier: Topic :: Internet :: Proxy Servers
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# selenium-proxy-tools

[![tests](https://github.com/SAKMZ/selenium-proxy-tools/actions/workflows/ci.yml/badge.svg)](https://github.com/SAKMZ/selenium-proxy-tools/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/selenium-proxy-tools.svg)](https://pypi.org/project/selenium-proxy-tools/)
[![Python](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

**Authenticated & rotating proxies made easy for Chrome-based browser automation**
— Selenium, [SeleniumBase](https://github.com/seleniumbase/SeleniumBase),
[undetected-chromedriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver),
Playwright, or plain `requests`.

Zero third-party dependencies. Standard library only.

---

## Why?

Proxies are a constant source of pain in browser automation:

| Problem | Chrome / Selenium out of the box | With `selenium-proxy-tools` |
| --- | --- | --- |
| **Authenticated SOCKS5/SOCKS4** | ❌ impossible — Chrome can't auth SOCKS at all | ✅ |
| **Authenticated HTTP/HTTPS** | ⚠️ needs a hand-built MV3 extension per session | ✅ |
| **Rotating pool / round-robin** | ❌ roll your own | ✅ built in |
| **DNS leaks** | ⚠️ | ✅ remote DNS via SOCKS5 |

It works by running a tiny **local, no-auth HTTP proxy** on a background thread
that forwards every connection through your real upstream proxy. You just point
the browser at the local address — no flags, no extensions, no external tools.

```
Chrome ──▶ 127.0.0.1:<port>  (local, no auth)
                 │
                 ▼
   your authenticated / rotating upstream proxy  ──▶  the internet
```

## Install

```bash
pip install selenium-proxy-tools
```

## Quick start

### SeleniumBase

```python
from seleniumbase import SB
from selenium_proxy_tools import ProxyRelay

with ProxyRelay("socks5://user:pass@host:1080") as relay:
    # relay.proxy -> "127.0.0.1:<port>"  (exactly what SeleniumBase expects)
    with SB(uc=True, proxy=relay.proxy) as sb:
        sb.open("https://api.ipify.org?format=json")
        print(sb.get_text("body"))
```

### undetected-chromedriver / plain Selenium

```python
import undetected_chromedriver as uc
from selenium_proxy_tools import ProxyRelay

# Works the same for an authenticated HTTP proxy: "http://user:pass@host:8080"
with ProxyRelay("socks5://user:pass@host:1080") as relay:
    options = uc.ChromeOptions()
    options.add_argument(f"--proxy-server={relay.url}")   # http://127.0.0.1:<port>
    driver = uc.Chrome(options=options)
    driver.get("https://api.ipify.org?format=json")
    print(driver.find_element("tag name", "body").text)
    driver.quit()
```

### Rotating pool

```python
from selenium_proxy_tools import ProxyRelay

pool = [
    "socks5://user:pass@a.example.com:1080",
    "http://user:pass@b.example.com:8080",     # mix schemes freely
    "socks5://user:pass@c.example.com:1080",
]

with ProxyRelay(pool, rotation="roundrobin") as relay:   # or rotation="random"
    with SB(uc=True, proxy=relay.proxy) as sb:
        ...   # each new connection exits through the next upstream
```

### requests / verify the exit IP

```python
import requests
from selenium_proxy_tools import ProxyRelay

with ProxyRelay("http://user:pass@host:8080") as relay:
    print(requests.get("https://api.ipify.org", proxies={"https": relay.url}).text)
    print("exit IP:", relay.exit_ip())
```

More runnable examples in [`examples/`](examples/).

## Supported upstreams

| Scheme | Auth | Notes |
| --- | --- | --- |
| `socks5://` | ✅ user/pass (RFC 1929) | remote DNS (no leaks) |
| `socks4://` | ✅ userid | `socks4a` remote DNS |
| `http://` / `https://` | ✅ Basic | `CONNECT` + absolute-form |

Accepted formats: `scheme://user:pass@host:port`, `scheme://host:port`,
`host:port:user:pass` (assumes SOCKS5), `host:port`.

## API

### `ProxyRelay(upstream, listen_host="127.0.0.1", listen_port=0, rotation=None)`

`upstream` is one proxy or a list of them. `rotation` is `None`, `"roundrobin"`,
or `"random"`.

| Member | Description |
| --- | --- |
| `.start(timeout=10.0)` | Start; blocks until bound. Returns `self`. |
| `.stop()` | Stop and release the local port. |
| `.proxy` | `"127.0.0.1:<port>"` — for SeleniumBase / uc `proxy=`. |
| `.url` | `"http://127.0.0.1:<port>"` — for `--proxy-server=`, requests, Playwright. |
| `.port` | The chosen local port (`None` until started). |
| `.exit_ip(timeout=20)` | The public IP currently seen through the relay. |
| context manager | `with ProxyRelay(...) as relay:` starts on enter, stops on exit. |

`SocksProxyRelay` is a backwards-compatible alias of `ProxyRelay`.

Also exported: `parse_upstream(str) -> UpstreamProxy` and
`get_exit_ip(proxy, timeout=20)`.

Manual lifecycle (reuse one relay across many browsers):

```python
relay = ProxyRelay("socks5://user:pass@host:1080").start()
try:
    ...   # use relay.proxy / relay.url as many times as you like
finally:
    relay.stop()
```

## How it works

- Binds a local `asyncio` TCP server on `127.0.0.1` (ephemeral port by default),
  running its own event loop on a **daemon thread** — so it plays nicely inside
  ordinary synchronous Selenium code.
- Speaks the **HTTP proxy** protocol locally: `CONNECT` for HTTPS tunnels,
  absolute-form for plain HTTP.
- Opens each connection through the selected upstream: **SOCKS5** (user/pass),
  **SOCKS4/4a**, or **HTTP/HTTPS** (`CONNECT` + `Proxy-Authorization`), then pipes
  bytes both ways.

## FAQ

**Does the local proxy need a password?** No — it listens on loopback only and
isn't reachable from outside your machine.

**HTTP and HTTPS both work?** Yes. HTTPS goes through `CONNECT` (end-to-end
encrypted; the relay only tunnels bytes).

**Do I need `pysocks`, `pproxy`, or anything else?** No — standard library only.

**Rotating residential proxies?** Either give a **pool** and use `rotation=`, or
if your provider rotates behind a fixed `host:port`, just keep one relay — each
connection exits via whatever IP the upstream currently serves.

## Development

```bash
git clone https://github.com/SAKMZ/selenium-proxy-tools
cd selenium-proxy-tools
python -m unittest discover -s tests -v
```

The suite spins up local SOCKS5-auth and HTTP-auth proxy servers, so the full
relay path (auth + rotation) is exercised without touching the network.

## License

[MIT](LICENSE) © Saif Ali (SAKMZ)
