Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ solve = nc.solve(
print(solve.token) # the hCaptcha token, ready to submit
print(solve.resp_key) # hcaptcha.getRespKey() equivalent, for sites that verify the pair
print(solve.user_agent) # submit the token with this User-Agent: some sites check the browser version
print(solve.warnings) # e.g. proxy_session_refused: tokens you reported from this proxy session were refused; a new one is more likely to pass
```

`solve()` submits the captcha and waits until it's done, using the API's long-poll so you aren't hammering it with requests. It returns the solved solve, or raises if the solve fails or your timeout runs out.
Expand Down
2 changes: 2 additions & 0 deletions src/nonecap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
SolvePage,
SolveStatus,
SolveType,
SolveWarning,
)
from ._version import __version__

Expand All @@ -72,6 +73,7 @@
"SolveErrorCode",
"SolveErrorReason",
"SolvePage",
"SolveWarning",
"Account",
"Proxy",
"SolveType",
Expand Down
22 changes: 21 additions & 1 deletion src/nonecap/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Literal, Optional, TypedDict, Union

Expand Down Expand Up @@ -84,6 +84,16 @@ class SolveError:
"""Reference for the codes and reasons."""


@dataclass(frozen=True)
class SolveWarning:
"""One advisory on a solve."""

code: str
"""``proxy_session_refused``, or a code this client does not know yet."""
message: str
"""What happened and what to do."""


@dataclass(frozen=True)
class Solve:
"""A solve resource, exactly as the API returns it."""
Expand Down Expand Up @@ -118,6 +128,11 @@ class Solve:
reject a token submitted by a different browser version, so send the token with this
as your ``User-Agent`` header (and from the same IP as your proxy, if you gave one).
Set alongside ``token``, otherwise None."""
warnings: list[SolveWarning] = field(default_factory=list)
"""Advisories about this solve; empty when there is none. ``proxy_session_refused``:
the last token(s) you reported from this solve's ``proxy`` session were rejected. A
new proxy session is more likely to pass, so switch to one for your next solves. The
solve still runs."""

@property
def is_terminal(self) -> bool:
Expand Down Expand Up @@ -153,6 +168,11 @@ def _from_dict(cls, data: dict[str, Any]) -> Solve:
queue_ms=data.get("queue_ms"),
resolve_ms=data.get("resolve_ms"),
user_agent=data.get("user_agent"),
warnings=[
SolveWarning(code=str(w.get("code", "")), message=str(w.get("message", "")))
for w in (data.get("warnings") or [])
if isinstance(w, dict)
],
)


Expand Down
2 changes: 1 addition & 1 deletion src/nonecap/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.8.5"
__version__ = "0.8.6"
10 changes: 10 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
SolveErrorReason,
SolveFailedError,
SolveTimeoutError,
SolveWarning,
UnsupportedMediaTypeError,
ValidationError,
)
Expand Down Expand Up @@ -85,6 +86,15 @@ def test_user_agent_is_none_until_solved_or_when_absent(self) -> None:
nc = client_for(Script((200, payload)))
assert nc.solves.retrieve("solve_1").user_agent is None

def test_returns_warnings_and_defaults_to_empty(self) -> None:
warning = {"code": "proxy_session_refused", "message": "Switch to a new proxy session."}
nc = client_for(Script((202, solve_payload(warnings=[warning]))))
assert nc.solves.retrieve("solve_1").warnings == [
SolveWarning(code="proxy_session_refused", message="Switch to a new proxy session.")
]
nc = client_for(Script((200, solve_payload())))
assert nc.solves.retrieve("solve_1").warnings == []


class TestCreate:
def test_posts_with_bearer_auth_and_json_body(self) -> None:
Expand Down
Loading