Metadata-Version: 2.4
Name: servicepipe-solver
Version: 0.1.0
Summary: Pure-Python solver for the ServicePipe anti-bot: arm a session (PoW + signed fingerprint) and solve the rotate-captcha, no browser in the request path.
Project-URL: Homepage, https://github.com/alexpervushin/servicepipe-solver
Project-URL: Source, https://github.com/alexpervushin/servicepipe-solver
License: MIT
License-File: LICENSE
Keywords: anti-bot,captcha,scraping,servicepipe,wasm
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.11
Requires-Dist: wasmtime>=46
Requires-Dist: wreq>=0.12
Provides-Extra: captcha
Requires-Dist: numpy>=1.24; extra == 'captcha'
Requires-Dist: pillow>=10; extra == 'captcha'
Requires-Dist: torch>=2.0; extra == 'captcha'
Requires-Dist: torchvision>=0.15; extra == 'captcha'
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: scrapy
Requires-Dist: scrapy-impersonate>=1.1; extra == 'scrapy'
Requires-Dist: scrapy>=2.11; extra == 'scrapy'
Description-Content-Type: text/markdown

# servicepipe-solver

Pure-Python **library** for the **ServicePipe** anti-bot (`servicepipe.ru`) used by various sites.
Given a URL it arms a session (cookies) the site accepts, solving both challenge paths — no browser,
no JS engine in the request path.

- **Flow A — computational arming** (the common path): compute the `spsc` proof-of-work + the signed
  browser-fingerprint cookie `spjs`, reload → real page. `wasmtime` runs the extracted challenge WASM.
- **Flow B — rotate-captcha** (*«Разверните картинку горизонтально»*): a local **RotNetR** model
  predicts the upright angle, the drag telemetry is synthesized, signed, and blessed.

## Install (uv)

```bash
uv add servicepipe-solver                 # core: flow A (wreq + wasmtime)
uv add "servicepipe-solver[captcha]"      # + flow B (torch RotNetR; model downloaded on first use)
uv add "servicepipe-solver[scrapy]"       # + the Scrapy integration (contrib.scrapy)
```

Python ≥3.11. The base install is light; `torch`/`torchvision` come only with `[captcha]`.

## Use

```python
import asyncio
from servicepipe_solver import solve

res = asyncio.run(solve("https://example.com/", allow_captcha=True))
# res.ok, res.flow (armed_a / captcha_b / open / forbidden / version_mismatch / failed)
# res.cookies -> {spsc, spid, spjs, ...}  attach to every request to that origin
# res.client  -> the armed wreq client (its cookie store) for reuse

if res.ok:
    page = await res.client.get(product_url, cookies=res.cookies)
```

The armed session = the cookie dict. **Attach `res.cookies` to every subsequent request** to that
origin (ServicePipe cookies are set by JS, not `Set-Cookie`); the session persists (one arm → many
real pages).

### Bring your own transport / proxy

The solver depends only on the `Transport` protocol; the default `WreqTransport` emulates Chrome's
TLS/HTTP2 fingerprint. Inject a pre-built client (e.g. with a proxy) or your own transport:

```python
import wreq
from servicepipe_solver import solve

client = wreq.Client(emulation=..., proxy="http://user:pass@host", cookie_store=True)
res = await solve(url, client=client)     # arming goes through YOUR client; reuse it for the crawl
```

> **Any transport you inject MUST present a real Chrome TLS/HTTP2 fingerprint.** ServicePipe blocks a
> plain-Python TLS handshake regardless of correct cookies — this is why the default uses `wreq`.

### Low-level engine (transport-free primitives)

If you already have a Chrome-fingerprinted transport (or a browser) and want to drive the protocol
yourself, `servicepipe_solver.engine` exposes the pure, no-networking primitives:

```python
from servicepipe_solver.engine import parse_inline, spsc, SpjsBuilder, CheckjsRuntime

ch = parse_inline(inline_script)          # you fetched the page yourself
sc = spsc(checkjs_text, ch.options)
spjs, _ = SpjsBuilder(CheckjsRuntime.load()).build(origin, ch.spid_cookie, sc)
```

### Scrapy

`servicepipe_solver.contrib.scrapy` (extra `[scrapy]`) ships two integration paths:

- `ServicePipeMiddleware` — a drop-in downloader middleware: arms out-of-band on a challenge and
  reissues the request with the armed cookies.
- `ScrapyTransport` — runs the arming loop *through* the crawler's downloader (inherits its
  middlewares/proxy/throttle).
