# Cloudflare Bypass with nodriver

Setup and usage reference for bypassing Cloudflare JS challenges using nodriver (Python).

## Why nodriver

nodriver (github.com/ultrafunkamsterdam/nodriver) is the successor to undetected-chromedriver. It talks directly to Chrome DevTools Protocol — no Selenium, no Playwright. In 2026 benchmarks it scored **28/31 Cloudflare targets passed, 0 blocked**.

## Installation

```bash
# Chromium browser (needed by nodriver)
sudo apt-get install -y chromium

# Python venv + nodriver
cd /path/to/project
python3 -m venv .venv-nodriver
.venv-nodriver/bin/pip install nodriver
```

## Working Python Script

Save as `nodriver_fetch.py` in the project root:

```python
#!/usr/bin/env python3
"""nodriver-based Cloudflare bypass. Called as subprocess.
Usage: ./nodriver_fetch.py <url> [timeout_seconds] [mode]
Mode: 'html' (default) or 'resolve'
Outputs JSON to stdout, errors to stderr.
"""
import asyncio, sys, json

async def fetch(url, timeout=15):
    import nodriver as nd
    browser = await nd.start(headless=True, sandbox=False, disable_gpu=True)
    try:
        tab = await browser.get(url)
        await tab.wait_for("body", timeout=timeout)
        await asyncio.sleep(3)
        html = await tab.evaluate("document.documentElement.outerHTML")
        if "Just a moment" in html[:500] or "Checking your browser" in html[:500]:
            await asyncio.sleep(5)
            html = await tab.evaluate("document.documentElement.outerHTML")
        return html
    finally:
        try: await browser.close()
        except: pass

async def resolve(url, timeout=20):
    import nodriver as nd
    browser = await nd.start(headless=True, sandbox=False, disable_gpu=True)
    try:
        tab = await browser.get(url)
        await tab.wait_for("body", timeout=timeout)
        await asyncio.sleep(4)
        sources = await tab.evaluate("""(() => {
            const f = [];
            document.querySelectorAll('video source, video').forEach(el => {
                const s = el.src || el.getAttribute('src') || '';
                if (s) f.push({type:'video', src:s});
            });
            document.querySelectorAll('[src*=\".m3u8\"], [href*=\".m3u8\"]').forEach(el => {
                const s = el.src || el.href || '';
                if (s) f.push({type:'m3u8', src:s});
            });
            document.querySelectorAll('script').forEach(s => {
                const m = s.textContent.match(/https?:[^'"\\s]+\\.m3u8[^'"\\s]*/g);
                if (m) m.forEach(u => f.push({type:'m3u8_script', src:u}));
            });
            return JSON.stringify(f);
        })()""")
        return json.loads(sources) if sources else []
    finally:
        try: await browser.close()
        except: pass

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(json.dumps({"success": False, "error": "Usage: nodriver_fetch.py <url> [timeout] [mode]"}))
        sys.exit(1)
    url, timeout = sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 15
    mode = sys.argv[3] if len(sys.argv) > 3 else "html"
    if mode == "resolve":
        print(json.dumps(asyncio.run(resolve(url, timeout))))
    else:
        html = asyncio.run(fetch(url, timeout))
        if "Just a moment" in html[:300] or "Checking your browser" in html[:300]:
            print(json.dumps({"success": False, "error": "Cloudflare not solved", "html_preview": html[:200]}))
        else:
            print(json.dumps({"success": True, "html_length": len(html), "html": html}))
```

## Go Integration

Call the script as a subprocess from Go:

```go
import "os/exec"

type nodriverResult struct {
    Success bool   `json:"success"`
    HTML    string `json:"html"`
    HTMLlen int    `json:"html_length"`
    Error   string `json:"error"`
}

func nodriverFetch(url string, timeout time.Duration) ([]byte, error) {
    script := "/path/to/nodriver_fetch.py"
    python := "/path/to/.venv-nodriver/bin/python"
    cmd := exec.Command(python, script, url, fmt.Sprintf("%.0f", timeout.Seconds()))
    output, err := cmd.Output()
    if err != nil { return nil, fmt.Errorf("nodriver: %w", err) }
    var r nodriverResult
    json.Unmarshal(output, &r)
    if !r.Success { return nil, fmt.Errorf("nodriver: %s", r.Error) }
    return []byte(r.HTML), nil
}
```

## Fallback Chain

```
Direct HTTP → uTLS → nodriver (Python subprocess) → iframe embed in browser
  500ms        1s           5-10s                    instant (user's browser)
```

The iframe embed fallback always works because the user's browser handles Cloudflare natively.
