nonecap 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonecap-0.1.0/.github/workflows/ci.yml +53 -0
- nonecap-0.1.0/.gitignore +13 -0
- nonecap-0.1.0/LICENSE +21 -0
- nonecap-0.1.0/PKG-INFO +190 -0
- nonecap-0.1.0/README.md +159 -0
- nonecap-0.1.0/pyproject.toml +74 -0
- nonecap-0.1.0/src/nonecap/__init__.py +66 -0
- nonecap-0.1.0/src/nonecap/_client.py +696 -0
- nonecap-0.1.0/src/nonecap/_errors.py +112 -0
- nonecap-0.1.0/src/nonecap/_types.py +127 -0
- nonecap-0.1.0/src/nonecap/_version.py +1 -0
- nonecap-0.1.0/src/nonecap/py.typed +0 -0
- nonecap-0.1.0/tests/__init__.py +0 -0
- nonecap-0.1.0/tests/conftest.py +63 -0
- nonecap-0.1.0/tests/test_async_client.py +94 -0
- nonecap-0.1.0/tests/test_client.py +240 -0
- nonecap-0.1.0/tests/typing_assertions.py +43 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python }}
|
|
23
|
+
- run: python -m pip install -e ".[dev]"
|
|
24
|
+
- run: ruff check .
|
|
25
|
+
- run: mypy
|
|
26
|
+
- run: pytest
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
# Tag pushes only (v0.1.0, v1.2.3, ...): build and publish to PyPI via
|
|
30
|
+
# trusted publishing (OIDC) — no token stored anywhere. One-time setup on
|
|
31
|
+
# PyPI: add this repo + workflow + the `pypi` environment as a (pending)
|
|
32
|
+
# trusted publisher for the `nonecap` project.
|
|
33
|
+
needs: test
|
|
34
|
+
if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment: pypi
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
- uses: actions/setup-python@v5
|
|
42
|
+
with:
|
|
43
|
+
python-version: "3.12"
|
|
44
|
+
- name: Verify tag matches package version
|
|
45
|
+
run: |
|
|
46
|
+
want="v$(python -c "import re;print(re.search(r'\"([^\"]+)\"', open('src/nonecap/_version.py').read()).group(1))")"
|
|
47
|
+
if [ "$want" != "$GITHUB_REF_NAME" ]; then
|
|
48
|
+
echo "::error::tag $GITHUB_REF_NAME does not match _version.py ($want). Bump the version or retag."
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
- run: python -m pip install build
|
|
52
|
+
- run: python -m build
|
|
53
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
nonecap-0.1.0/.gitignore
ADDED
nonecap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lunium
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
nonecap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nonecap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for the NoneCap hCaptcha solving API.
|
|
5
|
+
Project-URL: Homepage, https://nonecap.com
|
|
6
|
+
Project-URL: Documentation, https://nonecap.com/api-reference
|
|
7
|
+
Project-URL: Repository, https://github.com/nonecap/nonecap-py
|
|
8
|
+
Project-URL: Issues, https://github.com/nonecap/nonecap-py/issues
|
|
9
|
+
Author: Lunium
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api-client,captcha,captcha-solver,hcaptcha,hcaptcha-solver,nonecap,sdk
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: httpx>=0.24
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<h1 align="center">nonecap</h1>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://github.com/nonecap/nonecap-py/actions/workflows/ci.yml"><img src="https://github.com/nonecap/nonecap-py/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
36
|
+
<a href="https://pypi.org/project/nonecap/"><img src="https://img.shields.io/pypi/v/nonecap.svg" alt="PyPI"></a>
|
|
37
|
+
<a href="https://pypi.org/project/nonecap/"><img src="https://img.shields.io/pypi/pyversions/nonecap.svg" alt="Python versions"></a>
|
|
38
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<p align="center">Official Python client for the <a href="https://nonecap.com">NoneCap</a> hCaptcha solving API.</p>
|
|
42
|
+
|
|
43
|
+
Submit a captcha, get back a token. The client handles the polling, the timeouts, and the error cases so you don't write the request loop yourself. Sync and async, fully typed.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pip install nonecap
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Python 3.9+. The only dependency is [httpx](https://www.python-httpx.org/).
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
Grab an API key from [dashboard.nonecap.com](https://dashboard.nonecap.com), then:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from nonecap import NoneCap
|
|
59
|
+
|
|
60
|
+
nc = NoneCap(api_key="nc_live_...")
|
|
61
|
+
|
|
62
|
+
solve = nc.solve(
|
|
63
|
+
type="hcaptcha",
|
|
64
|
+
sitekey="10000000-ffff-ffff-ffff-000000000001",
|
|
65
|
+
url="https://example.com/login",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
print(solve.token) # the hCaptcha token, ready to submit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`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.
|
|
72
|
+
|
|
73
|
+
## Async
|
|
74
|
+
|
|
75
|
+
Same surface, `await`ed. Use it as an async context manager so the connection pool gets cleaned up:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import asyncio
|
|
79
|
+
from nonecap import AsyncNoneCap
|
|
80
|
+
|
|
81
|
+
async def main() -> None:
|
|
82
|
+
async with AsyncNoneCap(api_key="nc_live_...") as nc:
|
|
83
|
+
solve = await nc.solve(type="hcaptcha", sitekey="...", url="https://example.com")
|
|
84
|
+
print(solve.token)
|
|
85
|
+
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Handling failures
|
|
90
|
+
|
|
91
|
+
Every error this library raises extends `NoneCapError`, so you can catch the whole family or pick out the one you care about.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from nonecap import (
|
|
95
|
+
NoneCap,
|
|
96
|
+
SolveFailedError,
|
|
97
|
+
InsufficientCreditsError,
|
|
98
|
+
RateLimitError,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
solve = nc.solve(type="hcaptcha", sitekey=sitekey, url=url)
|
|
103
|
+
except SolveFailedError as err:
|
|
104
|
+
print("Could not solve it:", err.solve.error.code if err.solve.error else "?")
|
|
105
|
+
except InsufficientCreditsError:
|
|
106
|
+
print("Out of credits. Top up at dashboard.nonecap.com")
|
|
107
|
+
except RateLimitError:
|
|
108
|
+
print("Too many solves in flight, back off and retry")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The subclasses are `AuthenticationError` (401), `PermissionDeniedError` (403), `InsufficientCreditsError` (402), `ValidationError` (422/400, with a `param` naming the bad field), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `APIError` (5xx), `APIConnectionError` and `APITimeoutError` (the request never landed), and `SolveTimeoutError` (your `solve()` budget ran out). `SolveFailedError` carries the full `solve` so you can read the underlying error code and the timings.
|
|
112
|
+
|
|
113
|
+
## Enterprise captchas
|
|
114
|
+
|
|
115
|
+
For `hcaptcha_enterprise`, `rqdata` is required. The `@overload` signatures enforce that in mypy and pyright, so leaving it out fails your type check, and a runtime check backs it up before any network call:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
solve = nc.solve(
|
|
119
|
+
type="hcaptcha_enterprise",
|
|
120
|
+
sitekey=sitekey,
|
|
121
|
+
url=url,
|
|
122
|
+
rqdata="...", # required for enterprise
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Proxies
|
|
127
|
+
|
|
128
|
+
Pass a proxy as a dict or a URL string. The solve runs through it, and the bytes are metered back on the solve.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
nc.solve(
|
|
132
|
+
type="hcaptcha",
|
|
133
|
+
sitekey=sitekey,
|
|
134
|
+
url=url,
|
|
135
|
+
proxy={"scheme": "http", "host": "1.2.3.4", "port": 8080, "username": "u", "password": "p"},
|
|
136
|
+
# or: proxy="http://u:p@1.2.3.4:8080"
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Lower-level API
|
|
141
|
+
|
|
142
|
+
`solve()` is the convenient path. When you want control over submission and polling, the resource methods map one to one to the REST API:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# Submit without waiting: returns immediately with a pending solve
|
|
146
|
+
pending = nc.solves.create(type="hcaptcha", sitekey=sitekey, url=url)
|
|
147
|
+
|
|
148
|
+
# Submit and hold the connection up to 30s for it to finish
|
|
149
|
+
maybe_done = nc.solves.create(type="hcaptcha", sitekey=sitekey, url=url, wait=30)
|
|
150
|
+
|
|
151
|
+
# Poll one solve, long-polling up to 30s
|
|
152
|
+
solve = nc.solves.retrieve(pending.id, wait=30)
|
|
153
|
+
|
|
154
|
+
# Cancel a pending or in-flight solve
|
|
155
|
+
nc.solves.cancel(pending.id)
|
|
156
|
+
|
|
157
|
+
# List a page of solves
|
|
158
|
+
page = nc.solves.list(limit=50, status="solved")
|
|
159
|
+
|
|
160
|
+
# Or iterate every solve, newest first
|
|
161
|
+
for s in nc.solves.list_all():
|
|
162
|
+
print(s.id, s.status)
|
|
163
|
+
|
|
164
|
+
# Your account and credit balance
|
|
165
|
+
me = nc.me()
|
|
166
|
+
print(me.credits_balance)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
On `AsyncNoneCap` the same methods are coroutines, and `list_all()` is an async iterator (`async for s in nc.solves.list_all()`).
|
|
170
|
+
|
|
171
|
+
## Configuration
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
NoneCap(
|
|
175
|
+
api_key="nc_live_...", # required
|
|
176
|
+
base_url="https://api.nonecap.com", # override if you need to
|
|
177
|
+
timeout=100.0, # per HTTP request, seconds
|
|
178
|
+
http_client=my_httpx_client, # inject your own httpx.Client
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`solve()` takes its own `timeout` (seconds, default 180) for the overall wait.
|
|
183
|
+
|
|
184
|
+
## Typing
|
|
185
|
+
|
|
186
|
+
The package ships a `py.typed` marker and full inline annotations. Solves come back as frozen dataclasses with the exact field names the API uses (`solve.token`, `solve.credits_charged`, `solve.queue_ms`), so what you read in the [API reference](https://nonecap.com/api-reference) is what you get in code.
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT, see [LICENSE](LICENSE).
|
nonecap-0.1.0/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<h1 align="center">nonecap</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://github.com/nonecap/nonecap-py/actions/workflows/ci.yml"><img src="https://github.com/nonecap/nonecap-py/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
5
|
+
<a href="https://pypi.org/project/nonecap/"><img src="https://img.shields.io/pypi/v/nonecap.svg" alt="PyPI"></a>
|
|
6
|
+
<a href="https://pypi.org/project/nonecap/"><img src="https://img.shields.io/pypi/pyversions/nonecap.svg" alt="Python versions"></a>
|
|
7
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">Official Python client for the <a href="https://nonecap.com">NoneCap</a> hCaptcha solving API.</p>
|
|
11
|
+
|
|
12
|
+
Submit a captcha, get back a token. The client handles the polling, the timeouts, and the error cases so you don't write the request loop yourself. Sync and async, fully typed.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pip install nonecap
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Python 3.9+. The only dependency is [httpx](https://www.python-httpx.org/).
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
Grab an API key from [dashboard.nonecap.com](https://dashboard.nonecap.com), then:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from nonecap import NoneCap
|
|
28
|
+
|
|
29
|
+
nc = NoneCap(api_key="nc_live_...")
|
|
30
|
+
|
|
31
|
+
solve = nc.solve(
|
|
32
|
+
type="hcaptcha",
|
|
33
|
+
sitekey="10000000-ffff-ffff-ffff-000000000001",
|
|
34
|
+
url="https://example.com/login",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
print(solve.token) # the hCaptcha token, ready to submit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`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.
|
|
41
|
+
|
|
42
|
+
## Async
|
|
43
|
+
|
|
44
|
+
Same surface, `await`ed. Use it as an async context manager so the connection pool gets cleaned up:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import asyncio
|
|
48
|
+
from nonecap import AsyncNoneCap
|
|
49
|
+
|
|
50
|
+
async def main() -> None:
|
|
51
|
+
async with AsyncNoneCap(api_key="nc_live_...") as nc:
|
|
52
|
+
solve = await nc.solve(type="hcaptcha", sitekey="...", url="https://example.com")
|
|
53
|
+
print(solve.token)
|
|
54
|
+
|
|
55
|
+
asyncio.run(main())
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Handling failures
|
|
59
|
+
|
|
60
|
+
Every error this library raises extends `NoneCapError`, so you can catch the whole family or pick out the one you care about.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from nonecap import (
|
|
64
|
+
NoneCap,
|
|
65
|
+
SolveFailedError,
|
|
66
|
+
InsufficientCreditsError,
|
|
67
|
+
RateLimitError,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
solve = nc.solve(type="hcaptcha", sitekey=sitekey, url=url)
|
|
72
|
+
except SolveFailedError as err:
|
|
73
|
+
print("Could not solve it:", err.solve.error.code if err.solve.error else "?")
|
|
74
|
+
except InsufficientCreditsError:
|
|
75
|
+
print("Out of credits. Top up at dashboard.nonecap.com")
|
|
76
|
+
except RateLimitError:
|
|
77
|
+
print("Too many solves in flight, back off and retry")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The subclasses are `AuthenticationError` (401), `PermissionDeniedError` (403), `InsufficientCreditsError` (402), `ValidationError` (422/400, with a `param` naming the bad field), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `APIError` (5xx), `APIConnectionError` and `APITimeoutError` (the request never landed), and `SolveTimeoutError` (your `solve()` budget ran out). `SolveFailedError` carries the full `solve` so you can read the underlying error code and the timings.
|
|
81
|
+
|
|
82
|
+
## Enterprise captchas
|
|
83
|
+
|
|
84
|
+
For `hcaptcha_enterprise`, `rqdata` is required. The `@overload` signatures enforce that in mypy and pyright, so leaving it out fails your type check, and a runtime check backs it up before any network call:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
solve = nc.solve(
|
|
88
|
+
type="hcaptcha_enterprise",
|
|
89
|
+
sitekey=sitekey,
|
|
90
|
+
url=url,
|
|
91
|
+
rqdata="...", # required for enterprise
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Proxies
|
|
96
|
+
|
|
97
|
+
Pass a proxy as a dict or a URL string. The solve runs through it, and the bytes are metered back on the solve.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
nc.solve(
|
|
101
|
+
type="hcaptcha",
|
|
102
|
+
sitekey=sitekey,
|
|
103
|
+
url=url,
|
|
104
|
+
proxy={"scheme": "http", "host": "1.2.3.4", "port": 8080, "username": "u", "password": "p"},
|
|
105
|
+
# or: proxy="http://u:p@1.2.3.4:8080"
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Lower-level API
|
|
110
|
+
|
|
111
|
+
`solve()` is the convenient path. When you want control over submission and polling, the resource methods map one to one to the REST API:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# Submit without waiting: returns immediately with a pending solve
|
|
115
|
+
pending = nc.solves.create(type="hcaptcha", sitekey=sitekey, url=url)
|
|
116
|
+
|
|
117
|
+
# Submit and hold the connection up to 30s for it to finish
|
|
118
|
+
maybe_done = nc.solves.create(type="hcaptcha", sitekey=sitekey, url=url, wait=30)
|
|
119
|
+
|
|
120
|
+
# Poll one solve, long-polling up to 30s
|
|
121
|
+
solve = nc.solves.retrieve(pending.id, wait=30)
|
|
122
|
+
|
|
123
|
+
# Cancel a pending or in-flight solve
|
|
124
|
+
nc.solves.cancel(pending.id)
|
|
125
|
+
|
|
126
|
+
# List a page of solves
|
|
127
|
+
page = nc.solves.list(limit=50, status="solved")
|
|
128
|
+
|
|
129
|
+
# Or iterate every solve, newest first
|
|
130
|
+
for s in nc.solves.list_all():
|
|
131
|
+
print(s.id, s.status)
|
|
132
|
+
|
|
133
|
+
# Your account and credit balance
|
|
134
|
+
me = nc.me()
|
|
135
|
+
print(me.credits_balance)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
On `AsyncNoneCap` the same methods are coroutines, and `list_all()` is an async iterator (`async for s in nc.solves.list_all()`).
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
NoneCap(
|
|
144
|
+
api_key="nc_live_...", # required
|
|
145
|
+
base_url="https://api.nonecap.com", # override if you need to
|
|
146
|
+
timeout=100.0, # per HTTP request, seconds
|
|
147
|
+
http_client=my_httpx_client, # inject your own httpx.Client
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`solve()` takes its own `timeout` (seconds, default 180) for the overall wait.
|
|
152
|
+
|
|
153
|
+
## Typing
|
|
154
|
+
|
|
155
|
+
The package ships a `py.typed` marker and full inline annotations. Solves come back as frozen dataclasses with the exact field names the API uses (`solve.token`, `solve.credits_charged`, `solve.queue_ms`), so what you read in the [API reference](https://nonecap.com/api-reference) is what you get in code.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nonecap"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Official Python client for the NoneCap hCaptcha solving API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Lunium" }]
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
dependencies = ["httpx>=0.24"]
|
|
15
|
+
keywords = [
|
|
16
|
+
"nonecap",
|
|
17
|
+
"hcaptcha",
|
|
18
|
+
"captcha",
|
|
19
|
+
"captcha-solver",
|
|
20
|
+
"hcaptcha-solver",
|
|
21
|
+
"api-client",
|
|
22
|
+
"sdk",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"Operating System :: OS Independent",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.9",
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Typing :: Typed",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://nonecap.com"
|
|
39
|
+
Documentation = "https://nonecap.com/api-reference"
|
|
40
|
+
Repository = "https://github.com/nonecap/nonecap-py"
|
|
41
|
+
Issues = "https://github.com/nonecap/nonecap-py/issues"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=8",
|
|
46
|
+
"pytest-asyncio>=0.24",
|
|
47
|
+
"mypy>=1.13",
|
|
48
|
+
"ruff>=0.8",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.version]
|
|
52
|
+
path = "src/nonecap/_version.py"
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel]
|
|
55
|
+
packages = ["src/nonecap"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
asyncio_mode = "auto"
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
strict = true
|
|
63
|
+
warn_unused_ignores = true
|
|
64
|
+
files = ["src/nonecap", "tests/typing_assertions.py"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
line-length = 96
|
|
68
|
+
target-version = "py39"
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
select = ["E", "F", "W", "I", "UP", "B", "SIM"]
|
|
72
|
+
# UP006/UP007/UP045: keep typing.Optional/Union and dict/list generics as written —
|
|
73
|
+
# the floor is 3.9 and `X | None` in runtime positions needs 3.10.
|
|
74
|
+
ignore = ["UP006", "UP007", "UP045"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Official Python client for the NoneCap hCaptcha solving API.
|
|
2
|
+
|
|
3
|
+
>>> from nonecap import NoneCap
|
|
4
|
+
>>> nc = NoneCap(api_key="nc_live_...")
|
|
5
|
+
>>> solve = nc.solve(type="hcaptcha", sitekey="...", url="https://example.com")
|
|
6
|
+
>>> solve.token
|
|
7
|
+
'P1_...'
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from ._client import AsyncNoneCap, NoneCap
|
|
11
|
+
from ._errors import (
|
|
12
|
+
APIConnectionError,
|
|
13
|
+
APIError,
|
|
14
|
+
APITimeoutError,
|
|
15
|
+
AuthenticationError,
|
|
16
|
+
ConflictError,
|
|
17
|
+
InsufficientCreditsError,
|
|
18
|
+
NoneCapError,
|
|
19
|
+
NotFoundError,
|
|
20
|
+
PermissionDeniedError,
|
|
21
|
+
RateLimitError,
|
|
22
|
+
SolveFailedError,
|
|
23
|
+
SolveTimeoutError,
|
|
24
|
+
ValidationError,
|
|
25
|
+
)
|
|
26
|
+
from ._types import (
|
|
27
|
+
TERMINAL_STATUSES,
|
|
28
|
+
Account,
|
|
29
|
+
Proxy,
|
|
30
|
+
Solve,
|
|
31
|
+
SolveError,
|
|
32
|
+
SolvePage,
|
|
33
|
+
SolveStatus,
|
|
34
|
+
SolveType,
|
|
35
|
+
)
|
|
36
|
+
from ._version import __version__
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"__version__",
|
|
40
|
+
# clients
|
|
41
|
+
"NoneCap",
|
|
42
|
+
"AsyncNoneCap",
|
|
43
|
+
# types
|
|
44
|
+
"Solve",
|
|
45
|
+
"SolveError",
|
|
46
|
+
"SolvePage",
|
|
47
|
+
"Account",
|
|
48
|
+
"Proxy",
|
|
49
|
+
"SolveType",
|
|
50
|
+
"SolveStatus",
|
|
51
|
+
"TERMINAL_STATUSES",
|
|
52
|
+
# errors
|
|
53
|
+
"NoneCapError",
|
|
54
|
+
"AuthenticationError",
|
|
55
|
+
"PermissionDeniedError",
|
|
56
|
+
"InsufficientCreditsError",
|
|
57
|
+
"ValidationError",
|
|
58
|
+
"NotFoundError",
|
|
59
|
+
"ConflictError",
|
|
60
|
+
"RateLimitError",
|
|
61
|
+
"APIError",
|
|
62
|
+
"APIConnectionError",
|
|
63
|
+
"APITimeoutError",
|
|
64
|
+
"SolveFailedError",
|
|
65
|
+
"SolveTimeoutError",
|
|
66
|
+
]
|