botasaurus-turnstile 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.
@@ -0,0 +1,5 @@
1
+ dist/
2
+ build/
3
+ *.egg-info/
4
+ __pycache__/
5
+ *.pyc
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peak
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.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.5
2
+ Name: botasaurus-turnstile
3
+ Version: 0.1.0
4
+ Summary: Solve Cloudflare Turnstile in Botasaurus via the Peak API — read the sitekey, get a token, inject it. For when the anti-detect driver still stalls on the widget.
5
+ Project-URL: Homepage, https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile
6
+ Project-URL: Documentation, https://peak.fo/docs/turnstile?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile
7
+ Project-URL: Pricing, https://peak.fo/pricing?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile
8
+ Project-URL: Source, https://github.com/CircuitSavage/botasaurus-turnstile
9
+ Author: Peak
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: anti-detect,botasaurus,bypass,captcha,cf-clearance,cloudflare,scraping,solver,turnstile
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.8
21
+ Provides-Extra: botasaurus
22
+ Requires-Dist: botasaurus-driver>=4.0; extra == 'botasaurus'
23
+ Description-Content-Type: text/markdown
24
+
25
+ <a href="https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile">
26
+ <img src="https://raw.githubusercontent.com/CircuitSavage/botasaurus-turnstile/main/assets/peak-banner.png" alt="Peak — solve Cloudflare Turnstile & the 5s challenge in ~1s" width="100%">
27
+ </a>
28
+
29
+ # botasaurus-turnstile
30
+
31
+ **botasaurus-turnstile** solves Cloudflare Turnstile on a [Botasaurus](https://github.com/omkarcloud/botasaurus) driver by injecting a Peak-solved token, so a protected form or navigation proceeds.
32
+
33
+ ## Why
34
+
35
+ Botasaurus drives an anti-detect Chrome and ships a lot of Cloudflare handling, which is why people run it against protected sites. The interstitial often clears. The Turnstile *widget* on a datacenter IP is the part that doesn't — it renders, waits for a click or a token that never comes, and the run stalls.
36
+
37
+ This reads the sitekey from the DOM, gets a token from Peak, and injects it as the `cf-turnstile-response` value plus the widget's success callback. When the driver gets you to the page but the widget won't clear, drop in a Peak API key and it goes through.
38
+
39
+ ## Powered by Peak
40
+
41
+ This package uses [Peak](https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile) to solve Turnstile.
42
+ - Solve Cloudflare Turnstile & the 5s challenge in about a second
43
+ - $0.90 per 1,000 successful Turnstile solves, down to $0.35 at volume
44
+ - 1,000 free solves to start, no card.
45
+
46
+ → [Get your free API key](https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile) · [Docs](https://peak.fo/docs/turnstile?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile) · [Pricing](https://peak.fo/pricing?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile)
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install botasaurus-turnstile botasaurus-driver
52
+ ```
53
+
54
+ ## Quickstart
55
+
56
+ ```python
57
+ import os
58
+ from botasaurus.browser import browser, Driver
59
+ from botasaurus_turnstile import solve_turnstile
60
+
61
+ os.environ["PEAK_API_KEY"] = "pk_your_api_key"
62
+
63
+
64
+ @browser
65
+ def scrape(driver: Driver, data):
66
+ driver.get("https://protected.example/login")
67
+
68
+ # Reads the sitekey from the page, solves via Peak, injects the token.
69
+ solve_turnstile(driver)
70
+
71
+ # the form / navigation can now proceed
72
+
73
+
74
+ scrape()
75
+ ```
76
+
77
+ Works the same with a standalone `botasaurus_driver.Driver()` — anything that exposes `run_js` and `current_url`.
78
+
79
+ ## How it works
80
+
81
+ 1. **Read the sitekey.** A `run_js` DOM scan finds the `data-sitekey` on the `.cf-turnstile` widget, or the sitekey set through a JS `render()` call.
82
+ 2. **Solve via Peak.** `POST https://api.peak.fo/solve` with `task_type: "turnstiletask"`, the sitekey, and the driver's current URL. Pass `proxy=` to mint the token from your browser's egress IP.
83
+ 3. **Inject the token.** `run_js` sets every `cf-turnstile-response` field, fires `input`/`change`, and calls the widget's `data-callback` so the form or navigation continues.
84
+
85
+ ## API
86
+
87
+ ```python
88
+ solve_turnstile(
89
+ driver, # a Botasaurus Driver
90
+ api_key=None, # defaults to env PEAK_API_KEY
91
+ proxy=None, # optional http://user:pass@ip:port, forwarded to Peak
92
+ sitekey=None, # override auto-detection (e.g. widget in a frame)
93
+ url=None, # defaults to driver.current_url
94
+ task_type="turnstiletask",
95
+ timeout=180.0,
96
+ ) -> str # the injected token
97
+ ```
98
+
99
+ `read_sitekey(driver)` and `inject_token(driver, token)` are exposed for finer control. Peak also supports `task_type="cloudflare5stask"` for the 5s interstitial.
100
+
101
+ The API key is read from `PEAK_API_KEY` by default; you can also pass it explicitly. Never hardcode a real key.
102
+
103
+ ## Legitimate use
104
+
105
+ For automation, QA, and scraping public data you are allowed to access. Respect each target's Terms of Service and robots directives, and do not use this for credential-stuffing or other abuse.
106
+
107
+ ## License
108
+
109
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,85 @@
1
+ <a href="https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile">
2
+ <img src="https://raw.githubusercontent.com/CircuitSavage/botasaurus-turnstile/main/assets/peak-banner.png" alt="Peak — solve Cloudflare Turnstile & the 5s challenge in ~1s" width="100%">
3
+ </a>
4
+
5
+ # botasaurus-turnstile
6
+
7
+ **botasaurus-turnstile** solves Cloudflare Turnstile on a [Botasaurus](https://github.com/omkarcloud/botasaurus) driver by injecting a Peak-solved token, so a protected form or navigation proceeds.
8
+
9
+ ## Why
10
+
11
+ Botasaurus drives an anti-detect Chrome and ships a lot of Cloudflare handling, which is why people run it against protected sites. The interstitial often clears. The Turnstile *widget* on a datacenter IP is the part that doesn't — it renders, waits for a click or a token that never comes, and the run stalls.
12
+
13
+ This reads the sitekey from the DOM, gets a token from Peak, and injects it as the `cf-turnstile-response` value plus the widget's success callback. When the driver gets you to the page but the widget won't clear, drop in a Peak API key and it goes through.
14
+
15
+ ## Powered by Peak
16
+
17
+ This package uses [Peak](https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile) to solve Turnstile.
18
+ - Solve Cloudflare Turnstile & the 5s challenge in about a second
19
+ - $0.90 per 1,000 successful Turnstile solves, down to $0.35 at volume
20
+ - 1,000 free solves to start, no card.
21
+
22
+ → [Get your free API key](https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile) · [Docs](https://peak.fo/docs/turnstile?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile) · [Pricing](https://peak.fo/pricing?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile)
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install botasaurus-turnstile botasaurus-driver
28
+ ```
29
+
30
+ ## Quickstart
31
+
32
+ ```python
33
+ import os
34
+ from botasaurus.browser import browser, Driver
35
+ from botasaurus_turnstile import solve_turnstile
36
+
37
+ os.environ["PEAK_API_KEY"] = "pk_your_api_key"
38
+
39
+
40
+ @browser
41
+ def scrape(driver: Driver, data):
42
+ driver.get("https://protected.example/login")
43
+
44
+ # Reads the sitekey from the page, solves via Peak, injects the token.
45
+ solve_turnstile(driver)
46
+
47
+ # the form / navigation can now proceed
48
+
49
+
50
+ scrape()
51
+ ```
52
+
53
+ Works the same with a standalone `botasaurus_driver.Driver()` — anything that exposes `run_js` and `current_url`.
54
+
55
+ ## How it works
56
+
57
+ 1. **Read the sitekey.** A `run_js` DOM scan finds the `data-sitekey` on the `.cf-turnstile` widget, or the sitekey set through a JS `render()` call.
58
+ 2. **Solve via Peak.** `POST https://api.peak.fo/solve` with `task_type: "turnstiletask"`, the sitekey, and the driver's current URL. Pass `proxy=` to mint the token from your browser's egress IP.
59
+ 3. **Inject the token.** `run_js` sets every `cf-turnstile-response` field, fires `input`/`change`, and calls the widget's `data-callback` so the form or navigation continues.
60
+
61
+ ## API
62
+
63
+ ```python
64
+ solve_turnstile(
65
+ driver, # a Botasaurus Driver
66
+ api_key=None, # defaults to env PEAK_API_KEY
67
+ proxy=None, # optional http://user:pass@ip:port, forwarded to Peak
68
+ sitekey=None, # override auto-detection (e.g. widget in a frame)
69
+ url=None, # defaults to driver.current_url
70
+ task_type="turnstiletask",
71
+ timeout=180.0,
72
+ ) -> str # the injected token
73
+ ```
74
+
75
+ `read_sitekey(driver)` and `inject_token(driver, token)` are exposed for finer control. Peak also supports `task_type="cloudflare5stask"` for the 5s interstitial.
76
+
77
+ The API key is read from `PEAK_API_KEY` by default; you can also pass it explicitly. Never hardcode a real key.
78
+
79
+ ## Legitimate use
80
+
81
+ For automation, QA, and scraping public data you are allowed to access. Respect each target's Terms of Service and robots directives, and do not use this for credential-stuffing or other abuse.
82
+
83
+ ## License
84
+
85
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,34 @@
1
+ """botasaurus-turnstile: solve Cloudflare Turnstile in Botasaurus via Peak.
2
+
3
+ Public API::
4
+
5
+ from botasaurus_turnstile import solve_turnstile
6
+ token = solve_turnstile(driver, api_key="pk_...")
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .peak import (
12
+ DEFAULT_API_URL,
13
+ TASK_CLOUDFLARE_5S,
14
+ TASK_TURNSTILE,
15
+ PeakClient,
16
+ PeakError,
17
+ build_solve_payload,
18
+ )
19
+ from .solver import inject_token, read_sitekey, solve_turnstile
20
+
21
+ __version__ = "0.1.0"
22
+
23
+ __all__ = [
24
+ "solve_turnstile",
25
+ "read_sitekey",
26
+ "inject_token",
27
+ "PeakClient",
28
+ "PeakError",
29
+ "build_solve_payload",
30
+ "DEFAULT_API_URL",
31
+ "TASK_TURNSTILE",
32
+ "TASK_CLOUDFLARE_5S",
33
+ "__version__",
34
+ ]
@@ -0,0 +1,110 @@
1
+ """Peak API client for solving Cloudflare Turnstile.
2
+
3
+ Kept free of any Botasaurus dependency so it can be unit tested and reused
4
+ on its own. The HTTP call uses the standard library only.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import urllib.error
11
+ import urllib.request
12
+ from typing import Optional
13
+
14
+ DEFAULT_API_URL = "https://api.peak.fo/solve"
15
+
16
+ # task_type sent to Peak for the Turnstile widget challenge.
17
+ TASK_TURNSTILE = "turnstiletask"
18
+ # Peak also supports the 5s interstitial via this task_type.
19
+ TASK_CLOUDFLARE_5S = "cloudflare5stask"
20
+
21
+
22
+ class PeakError(RuntimeError):
23
+ """Raised when Peak cannot solve the challenge or the call fails."""
24
+
25
+
26
+ def build_solve_payload(
27
+ sitekey: str,
28
+ url: str,
29
+ proxy: Optional[str] = None,
30
+ task_type: str = TASK_TURNSTILE,
31
+ ) -> dict:
32
+ """Build the JSON body for POST https://api.peak.fo/solve.
33
+
34
+ ``proxy`` is omitted entirely when not provided, per the Peak contract.
35
+ """
36
+ payload = {
37
+ "task_type": task_type,
38
+ "sitekey": sitekey,
39
+ "url": url,
40
+ }
41
+ if proxy:
42
+ payload["proxy"] = proxy
43
+ return payload
44
+
45
+
46
+ class PeakClient:
47
+ """Thin client around the Peak solve endpoint."""
48
+
49
+ def __init__(
50
+ self,
51
+ api_key: str,
52
+ api_url: str = DEFAULT_API_URL,
53
+ proxy: Optional[str] = None,
54
+ timeout: float = 180.0,
55
+ ) -> None:
56
+ if not api_key:
57
+ raise PeakError(
58
+ "No Peak API key. Set PEAK_API_KEY (get a free key at "
59
+ "https://peak.fo)."
60
+ )
61
+ self.api_key = api_key
62
+ self.api_url = api_url
63
+ self.proxy = proxy
64
+ self.timeout = timeout
65
+
66
+ def _post(self, payload: dict) -> dict:
67
+ data = json.dumps(payload).encode("utf-8")
68
+ req = urllib.request.Request(
69
+ self.api_url,
70
+ data=data,
71
+ method="POST",
72
+ headers={
73
+ "Content-Type": "application/json",
74
+ "X-API-Key": self.api_key,
75
+ "User-Agent": "botasaurus-turnstile/0.1.0 (+https://github.com/CircuitSavage/botasaurus-turnstile)",
76
+ },
77
+ )
78
+ try:
79
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
80
+ body = resp.read().decode("utf-8")
81
+ except urllib.error.HTTPError as exc: # pragma: no cover - network path
82
+ body = exc.read().decode("utf-8", "replace")
83
+ except urllib.error.URLError as exc: # pragma: no cover - network path
84
+ raise PeakError(f"Peak request failed: {exc}") from exc
85
+ try:
86
+ return json.loads(body)
87
+ except ValueError as exc: # pragma: no cover - defensive
88
+ raise PeakError(f"Peak returned non-JSON response: {body!r}") from exc
89
+
90
+ def solve(
91
+ self,
92
+ sitekey: str,
93
+ url: str,
94
+ proxy: Optional[str] = None,
95
+ task_type: str = TASK_TURNSTILE,
96
+ ) -> str:
97
+ """Solve a Turnstile challenge and return the token string.
98
+
99
+ Raises :class:`PeakError` on failure.
100
+ """
101
+ payload = build_solve_payload(
102
+ sitekey, url, proxy=proxy or self.proxy, task_type=task_type
103
+ )
104
+ result = self._post(payload)
105
+ if not result.get("success"):
106
+ raise PeakError(result.get("error") or "Peak solve failed")
107
+ token = (result.get("data") or {}).get("token")
108
+ if not token:
109
+ raise PeakError("Peak response missing data.token")
110
+ return token
@@ -0,0 +1,123 @@
1
+ """Solve Cloudflare Turnstile on a live Botasaurus driver via the Peak API.
2
+
3
+ Botasaurus drives an anti-detect Chrome and is a common pick against
4
+ Cloudflare. When the Turnstile widget still stalls, this reads the sitekey,
5
+ gets a token from Peak, and injects it so the form or navigation proceeds.
6
+
7
+ Works with the Botasaurus ``Driver`` (uses ``run_js`` and ``current_url``).
8
+ Botasaurus itself is an optional import.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import os
15
+ from typing import Optional
16
+
17
+ from .peak import DEFAULT_API_URL, TASK_TURNSTILE, PeakClient, PeakError
18
+
19
+ _READ_SITEKEY_JS = r"""
20
+ var el = document.querySelector('[data-sitekey]');
21
+ if (el) { return el.getAttribute('data-sitekey'); }
22
+ var m = (document.documentElement.outerHTML || '').match(
23
+ /(?:sitekey|render)\s*[:=]\s*["']((?:0x|1x)[^"']+)["']/i);
24
+ return m ? m[1] : null;
25
+ """
26
+
27
+ # Injects a solved token: sets every cf-turnstile-response field, fires
28
+ # input/change so listeners react, and calls the widget's success callback.
29
+ # The token is embedded as a JSON literal in inject_token.
30
+ _INJECT_TOKEN_BODY = r"""
31
+ var fields = document.querySelectorAll(
32
+ 'input[name="cf-turnstile-response"], textarea[name="cf-turnstile-response"], ' +
33
+ 'input#cf-turnstile-response, [name="cf-turnstile-response"]');
34
+ if (!fields.length) {
35
+ var holder = document.querySelector('.cf-turnstile') || document.body;
36
+ var inp = document.createElement('input');
37
+ inp.type = 'hidden';
38
+ inp.name = 'cf-turnstile-response';
39
+ holder.appendChild(inp);
40
+ fields = [inp];
41
+ }
42
+ fields.forEach(function (f) {
43
+ f.value = token;
44
+ f.dispatchEvent(new Event('input', { bubbles: true }));
45
+ f.dispatchEvent(new Event('change', { bubbles: true }));
46
+ });
47
+ var called = false;
48
+ document.querySelectorAll('[data-callback]').forEach(function (w) {
49
+ var name = w.getAttribute('data-callback');
50
+ if (name && typeof window[name] === 'function') {
51
+ window[name](token);
52
+ called = true;
53
+ }
54
+ });
55
+ return called;
56
+ """
57
+
58
+
59
+ def read_sitekey(driver) -> Optional[str]:
60
+ """Return the Turnstile sitekey on the current page, or None."""
61
+ sitekey = driver.run_js(_READ_SITEKEY_JS)
62
+ if sitekey:
63
+ return str(sitekey).strip()
64
+ return None
65
+
66
+
67
+ def inject_token(driver, token: str) -> None:
68
+ """Inject a solved token into the page's Turnstile widget."""
69
+ driver.run_js("var token = " + json.dumps(token) + ";\n" + _INJECT_TOKEN_BODY)
70
+
71
+
72
+ def _current_url(driver) -> Optional[str]:
73
+ try:
74
+ url = getattr(driver, "current_url", None)
75
+ if url:
76
+ return url
77
+ except Exception:
78
+ pass
79
+ try:
80
+ return driver.run_js("return window.location.href")
81
+ except Exception:
82
+ return None
83
+
84
+
85
+ def solve_turnstile(
86
+ driver,
87
+ api_key: Optional[str] = None,
88
+ proxy: Optional[str] = None,
89
+ sitekey: Optional[str] = None,
90
+ url: Optional[str] = None,
91
+ task_type: str = TASK_TURNSTILE,
92
+ api_url: Optional[str] = None,
93
+ timeout: float = 180.0,
94
+ ) -> str:
95
+ """Solve the Turnstile challenge on ``driver``'s current page.
96
+
97
+ Reads the sitekey from the DOM (unless ``sitekey`` is given), asks Peak
98
+ for a token, injects it, and returns the token. ``api_key`` defaults to
99
+ the ``PEAK_API_KEY`` environment variable; ``url`` defaults to the
100
+ driver's current URL. Pass ``proxy`` to mint the token from the browser's
101
+ egress IP; omit it and Peak solves without one.
102
+
103
+ Raises :class:`~botasaurus_turnstile.peak.PeakError` on failure.
104
+ """
105
+ api_key = api_key or os.environ.get("PEAK_API_KEY")
106
+
107
+ if sitekey is None:
108
+ sitekey = read_sitekey(driver)
109
+ if not sitekey:
110
+ raise PeakError(
111
+ "No Turnstile sitekey found on the page. Pass sitekey= "
112
+ "explicitly if the widget renders in a frame or after load."
113
+ )
114
+
115
+ if url is None:
116
+ url = _current_url(driver)
117
+
118
+ client = PeakClient(
119
+ api_key, api_url=api_url or DEFAULT_API_URL, proxy=proxy, timeout=timeout
120
+ )
121
+ token = client.solve(sitekey, url, proxy=proxy, task_type=task_type)
122
+ inject_token(driver, token)
123
+ return token
@@ -0,0 +1,18 @@
1
+ import os
2
+
3
+ from botasaurus.browser import browser, Driver
4
+ from botasaurus_turnstile import solve_turnstile
5
+
6
+ # Get a free key at https://peak.fo
7
+ os.environ.setdefault("PEAK_API_KEY", "pk_your_api_key")
8
+
9
+
10
+ @browser
11
+ def scrape(driver: Driver, data):
12
+ driver.get("https://protected.example/login")
13
+ token = solve_turnstile(driver)
14
+ print("solved, token length:", len(token))
15
+
16
+
17
+ if __name__ == "__main__":
18
+ scrape()
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "botasaurus-turnstile"
7
+ version = "0.1.0"
8
+ description = "Solve Cloudflare Turnstile in Botasaurus via the Peak API — read the sitekey, get a token, inject it. For when the anti-detect driver still stalls on the widget."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.8"
12
+ authors = [{ name = "Peak" }]
13
+ keywords = [
14
+ "botasaurus",
15
+ "cloudflare",
16
+ "turnstile",
17
+ "captcha",
18
+ "solver",
19
+ "bypass",
20
+ "cf-clearance",
21
+ "scraping",
22
+ "anti-detect",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 4 - Beta",
26
+ "Intended Audience :: Developers",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Topic :: Internet :: WWW/HTTP :: Browsers",
31
+ "Topic :: Software Development :: Testing",
32
+ ]
33
+ dependencies = []
34
+
35
+ [project.optional-dependencies]
36
+ botasaurus = ["botasaurus-driver>=4.0"]
37
+
38
+ [project.urls]
39
+ Homepage = "https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile"
40
+ Documentation = "https://peak.fo/docs/turnstile?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile"
41
+ Pricing = "https://peak.fo/pricing?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=botasaurus-turnstile"
42
+ Source = "https://github.com/CircuitSavage/botasaurus-turnstile"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["botasaurus_turnstile"]