diff --git a/client/pyproject.toml b/client/pyproject.toml index 7c03b9c..7732dc0 100644 --- a/client/pyproject.toml +++ b/client/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "robotex" -version = "0.2.0" +version = "0.2.1" description = "Python-клиент Robotex API: браузер с обходом антиботов, задачи v2 и окна сессий" readme = "README.md" requires-python = ">=3.10" diff --git a/client/src/robotex/__init__.py b/client/src/robotex/__init__.py index cf9a079..1a9dca2 100644 --- a/client/src/robotex/__init__.py +++ b/client/src/robotex/__init__.py @@ -21,7 +21,7 @@ from robotex.errors import ( ) from robotex.models import ActionResult, Result, Task -__version__ = "0.2.0" +__version__ = "0.2.1" __all__ = [ "Action", "ActionResult", "AsyncRobotex", "AsyncSession", "AsyncTaskRef", "AuthError", "InvalidRequest", diff --git a/client/src/robotex/models.py b/client/src/robotex/models.py index 1588a32..e6951c1 100644 --- a/client/src/robotex/models.py +++ b/client/src/robotex/models.py @@ -24,6 +24,7 @@ class Result: code: str | None = None error: str | None = None user_agent: str | None = None + url: str | None = None cookies: list[dict[str, Any]] = field(default_factory=list) actions: list[ActionResult] = field(default_factory=list) html: str | None = None @@ -59,6 +60,7 @@ class Result: code=data.get("code"), error=data.get("error"), user_agent=data.get("user_agent"), + url=data.get("url"), cookies=data.get("cookies") or [], actions=[ActionResult.from_dict(a) for a in data.get("actions") or []], html=data.get("html"), diff --git a/docs/API.md b/docs/API.md index 7a9a985..1045de4 100644 --- a/docs/API.md +++ b/docs/API.md @@ -121,6 +121,7 @@ Cookie: session_id= # необязательно, см. «Сесси | `status` | `"ok"` — антибот пройден и все действия выполнены, иначе `"err"` | | `code` | при `"err"`: причина, см. таблицу ниже | | `error` | при `"err"`: текст ошибки; для упавшего действия — `: <текст>` | +| `url` | адрес страницы после выполнения действий (SPA меняет его без навигации, например на страницу созданной заявки) | | `user_agent` | родной UA браузера, с которым проходили антибот. Используйте его в своём HTTP-клиенте вместе с `cf_clearance` (см. [Usage.md](Usage.md)) | | `cookies` | все cookies контекста | | `actions` | по элементу на выполненное действие: `type`, `ok`, `result` (результат или текст ошибки), `duration_ms` | diff --git a/src/api/schemas.py b/src/api/schemas.py index 23e4ed9..3581096 100644 --- a/src/api/schemas.py +++ b/src/api/schemas.py @@ -161,6 +161,8 @@ class SolveResponse(BaseModel): code: Literal["no_slots", "request_failed", "challenge_failed", "action_failed"] | None = None error: str | None = None user_agent: str | None = None + # адрес страницы после выполнения действий + url: str | None = None cookies: list[dict[str, Any]] | None = None actions: list[ActionResult] = [] html: str | None = None diff --git a/src/worker/windows.py b/src/worker/windows.py index c41d203..aa1b053 100644 --- a/src/worker/windows.py +++ b/src/worker/windows.py @@ -138,6 +138,8 @@ def make_page_action(payload: TaskRequest): "html": await page.inner_html("html"), "screenshot": screenshot, "user_agent": await page.evaluate("() => navigator.userAgent"), + # адрес страницы после действий: SPA меняет его без навигации (номер заявки и т.п.) + "url": page.url, } return page_action @@ -149,6 +151,7 @@ def build_result(payload: TaskRequest, result: dict) -> dict: response = { "status": "ok", "user_agent": result["user_agent"], + "url": result.get("url"), "cookies": result["cookies"], "actions": result["actions"], } diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 3377a0f..825d90c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -21,6 +21,8 @@ class FakeContext: class FakePage: + url = "https://example.com/after-actions" + def __init__(self, context): self.context = context diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 43d628e..694e56c 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -69,6 +69,7 @@ def test_run(rx, monkeypatch): result = rx.run("https://example.com", [Action.inner_text("h1")], html=True) assert result.ok and result.results == ["Example"] assert result.html == "page" and result.user_agent == "UA/engine" + assert result.url == "https://example.com/after-actions" assert result.cookie("cf_clearance") == "secret-cookie" assert result.session_id diff --git a/tests/unit/test_v1.py b/tests/unit/test_v1.py index 7a0d6f3..2e78dd1 100644 --- a/tests/unit/test_v1.py +++ b/tests/unit/test_v1.py @@ -25,6 +25,7 @@ def test_ok(env, monkeypatch): assert data["status"] == "ok" assert "code" not in data and "error" not in data assert data["user_agent"] == "UA/engine" + assert data["url"] == "https://example.com/after-actions" assert data["actions"] == [{"type": "wait_for", "ok": True, "duration_ms": 5}] assert data["html"] == "page" assert data["screenshot"] == "cG5n" # base64(b"png")