nodriver-proxyhat 0.1.0__py3-none-any.whl

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,15 @@
1
+ """nodriver-proxyhat — route nodriver (undetected Chrome) through ProxyHat residential proxies."""
2
+
3
+ from nodriver_proxyhat._auth import enable_proxy_auth
4
+ from nodriver_proxyhat._resolve import ProxyHatConfigError, resolve_credentials
5
+ from nodriver_proxyhat.proxy import proxyhat_auth, proxyhat_browser, proxyhat_browser_args
6
+
7
+ __all__ = [
8
+ "ProxyHatConfigError",
9
+ "enable_proxy_auth",
10
+ "proxyhat_auth",
11
+ "proxyhat_browser",
12
+ "proxyhat_browser_args",
13
+ "resolve_credentials",
14
+ ]
15
+ __version__ = "0.1.0"
@@ -0,0 +1,55 @@
1
+ """Wire ProxyHat gateway authentication onto a nodriver tab over raw CDP.
2
+
3
+ Chrome's ``--proxy-server`` flag can't carry a username/password, so a credentialed
4
+ residential gateway needs another way to answer the proxy's auth challenge. nodriver
5
+ speaks the Chrome DevTools Protocol directly, so we authenticate the CDP way:
6
+
7
+ 1. enable the ``Fetch`` domain with ``handle_auth_requests=True``,
8
+ 2. answer every ``Fetch.authRequired`` with the ProxyHat targeting username +
9
+ sub-user password via ``Fetch.continueWithAuth`` (``ProvideCredentials``),
10
+ 3. resume every other paused request with ``Fetch.continueRequest``.
11
+
12
+ Enabling ``Fetch`` pauses *every* request (a ``requestPaused`` event), so the
13
+ non-auth requests must be continued too or the page hangs waiting on us.
14
+
15
+ This is the HTTP gateway path (port 8080) — CDP proxy auth answers the HTTP
16
+ proxy's basic-auth challenge. ``nodriver`` is imported lazily so importing this
17
+ module never requires it.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import TYPE_CHECKING
23
+
24
+ if TYPE_CHECKING:
25
+ from nodriver import Tab
26
+
27
+
28
+ async def enable_proxy_auth(tab: Tab, username: str, password: str) -> None:
29
+ """Register the CDP proxy-auth handlers on ``tab`` and enable ``Fetch``.
30
+
31
+ Adds a ``Fetch.authRequired`` handler that answers with ``username`` /
32
+ ``password`` and a ``Fetch.requestPaused`` handler that resumes normal
33
+ requests, then enables the Fetch domain with auth handling. Call once per tab
34
+ before navigating so the very first request is authenticated.
35
+ """
36
+ from nodriver import cdp
37
+
38
+ async def _on_auth_required(event: cdp.fetch.AuthRequired) -> None:
39
+ await tab.send(
40
+ cdp.fetch.continue_with_auth(
41
+ request_id=event.request_id,
42
+ auth_challenge_response=cdp.network.AuthChallengeResponse(
43
+ response="ProvideCredentials",
44
+ username=username,
45
+ password=password,
46
+ ),
47
+ )
48
+ )
49
+
50
+ async def _on_request_paused(event: cdp.fetch.RequestPaused) -> None:
51
+ await tab.send(cdp.fetch.continue_request(request_id=event.request_id))
52
+
53
+ tab.add_handler(cdp.fetch.AuthRequired, _on_auth_required)
54
+ tab.add_handler(cdp.fetch.RequestPaused, _on_request_paused)
55
+ await tab.send(cdp.fetch.enable(handle_auth_requests=True))
@@ -0,0 +1,71 @@
1
+ """Gateway credential resolution for nodriver-proxyhat.
2
+
3
+ Mirrors the other ProxyHat integrations: explicit ``username``/``password``
4
+ (or ``PROXYHAT_USERNAME``/``PROXYHAT_PASSWORD``) win; otherwise an API key
5
+ (``PROXYHAT_API_KEY``) looks up your sub-users via the official ``proxyhat`` SDK
6
+ and picks an active one with remaining traffic — or the one named by ``sub_user``.
7
+ Everything here except the sub-user lookup is offline.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+
14
+ from proxyhat import ProxyHat
15
+
16
+
17
+ class ProxyHatConfigError(RuntimeError):
18
+ """Raised when ProxyHat credentials cannot be resolved."""
19
+
20
+
21
+ def _env(name: str) -> str | None:
22
+ value = os.environ.get(name)
23
+ return value.strip() if value and value.strip() else None
24
+
25
+
26
+ def resolve_credentials(
27
+ *,
28
+ api_key: str | None = None,
29
+ username: str | None = None,
30
+ password: str | None = None,
31
+ sub_user: str | None = None,
32
+ ) -> tuple[str, str]:
33
+ """Resolve a sub-user's ``(proxy_username, proxy_password)``.
34
+
35
+ Options win over environment variables. Precedence:
36
+
37
+ 1. explicit ``username`` + ``password`` (``PROXYHAT_USERNAME`` / ``PROXYHAT_PASSWORD``)
38
+ 2. ``api_key`` (``PROXYHAT_API_KEY``) → auto-pick an active sub-user, or the
39
+ one named by ``sub_user`` (``PROXYHAT_SUBUSER``).
40
+ """
41
+ username = username or _env("PROXYHAT_USERNAME")
42
+ password = password or _env("PROXYHAT_PASSWORD")
43
+ if username and password:
44
+ return username, password
45
+
46
+ api_key = api_key or _env("PROXYHAT_API_KEY")
47
+ if not api_key:
48
+ raise ProxyHatConfigError(
49
+ "nodriver-proxyhat: no credentials. Pass api_key (or PROXYHAT_API_KEY), "
50
+ "or username + password (PROXYHAT_USERNAME / PROXYHAT_PASSWORD)."
51
+ )
52
+
53
+ return _resolve_sub_user(api_key, sub_user or _env("PROXYHAT_SUBUSER"))
54
+
55
+
56
+ def _resolve_sub_user(api_key: str, want: str | None) -> tuple[str, str]:
57
+ users = ProxyHat(api_key=api_key).sub_users.list()
58
+ usable = [u for u in users if not u.suspended_at and (u.traffic_limit == 0 or u.used_traffic < u.traffic_limit)]
59
+ if want:
60
+ chosen = next((u for u in users if u.uuid == want or u.name == want), None)
61
+ else:
62
+ chosen = usable[0] if usable else None
63
+
64
+ if chosen is None or not chosen.proxy_username or not chosen.proxy_password:
65
+ raise ProxyHatConfigError(
66
+ f'nodriver-proxyhat: no sub-user matched "{want}" (or it has no proxy credentials).'
67
+ if want
68
+ else "nodriver-proxyhat: no usable sub-user found (all suspended or out of traffic). "
69
+ "Create one, top up, or pass sub_user."
70
+ )
71
+ return chosen.proxy_username, chosen.proxy_password
@@ -0,0 +1,162 @@
1
+ """Route a nodriver (undetected Chrome) browser through the ProxyHat gateway.
2
+
3
+ nodriver launches Chrome with ``nodriver.start(browser_args=[...])``, but Chrome's
4
+ ``--proxy-server`` flag can't carry a username/password. nodriver gives raw CDP
5
+ access, so the credentialed residential gateway is authenticated the CDP way:
6
+ enable the Fetch domain with ``handle_auth_requests=True`` and answer
7
+ ``Fetch.authRequired`` with the ProxyHat targeting username + sub-user password
8
+ (see :mod:`nodriver_proxyhat._auth`).
9
+
10
+ Three entry points:
11
+
12
+ - ``proxyhat_browser_args()`` — the ``--proxy-server`` launch flag for the gateway.
13
+ - ``proxyhat_auth()`` — the ``(username, password)`` for wiring the CDP auth
14
+ handler yourself.
15
+ - ``proxyhat_browser()`` — start Chrome with the flag applied *and* the CDP auth
16
+ handler already wired.
17
+
18
+ This is the HTTP gateway (port 8080); CDP proxy auth applies to the HTTP proxy.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import TYPE_CHECKING, Any
24
+
25
+ from proxyhat import (
26
+ PROXYHAT_GATEWAY,
27
+ PROXYHAT_PORT_HTTP,
28
+ build_proxy_username,
29
+ )
30
+
31
+ from nodriver_proxyhat._auth import enable_proxy_auth
32
+ from nodriver_proxyhat._resolve import resolve_credentials
33
+
34
+ if TYPE_CHECKING:
35
+ from nodriver import Browser
36
+
37
+ # A browser session takes many steps against the same site, so pin one
38
+ # residential IP for the whole session by default — keeps cookies and
39
+ # fingerprint consistent. sticky=False rotates a fresh IP per connection.
40
+ DEFAULT_STICKY = "30m"
41
+
42
+
43
+ def proxyhat_browser_args(**_targeting: Any) -> list[str]:
44
+ """Return the Chrome launch args pointing nodriver at the ProxyHat gateway.
45
+
46
+ Just ``["--proxy-server=gate.proxyhat.com:8080"]`` — the HTTP gateway host and
47
+ port. Targeting (``country`` / ``region`` / ``city`` / ``sticky`` / ``filter``)
48
+ is *not* encoded here, because Chrome's ``--proxy-server`` can't carry it: it
49
+ lives in the CDP auth username built by :func:`proxyhat_auth` and applied by
50
+ :func:`proxyhat_browser`. Targeting keyword arguments are accepted (so you can
51
+ splat the same dict you pass to those) but do not change the returned flag.
52
+
53
+ Merge the result into your own ``nodriver.start(browser_args=[...])`` call and
54
+ wire auth yourself with :func:`proxyhat_auth` + ``enable_proxy_auth`` — or just
55
+ use :func:`proxyhat_browser`, which does both.
56
+ """
57
+ return [f"--proxy-server={PROXYHAT_GATEWAY}:{PROXYHAT_PORT_HTTP}"]
58
+
59
+
60
+ def proxyhat_auth(
61
+ *,
62
+ api_key: str | None = None,
63
+ username: str | None = None,
64
+ password: str | None = None,
65
+ sub_user: str | None = None,
66
+ country: str | None = None,
67
+ region: str | None = None,
68
+ city: str | None = None,
69
+ sticky: bool | str | None = DEFAULT_STICKY,
70
+ filter: str | None = None,
71
+ ) -> tuple[str, str]:
72
+ """Resolve the ``(gateway_username, password)`` for the ProxyHat gateway.
73
+
74
+ Resolves credentials (``api_key`` / ``PROXYHAT_API_KEY`` auto-picks an active
75
+ sub-user, or pass explicit ``username`` / ``password``), then builds the
76
+ targeting username — geo and stickiness reflected — with the official
77
+ ``proxyhat`` SDK grammar. Feed the pair to a CDP ``Fetch.authRequired`` handler
78
+ (``ProvideCredentials``) if you're wiring nodriver yourself; otherwise
79
+ :func:`proxyhat_browser` does it for you.
80
+
81
+ Sticky vs rotating:
82
+
83
+ - ``sticky="30m"`` (default) or ``sticky=True`` pins one residential IP for
84
+ the session — recommended for a browser.
85
+ - ``sticky=False`` (or ``None``) rotates: a fresh residential IP per connection.
86
+ - ``sticky="2h"`` sets a custom session lifetime.
87
+
88
+ Geo/quality targeting: ``country`` (ISO code or ``"any"``), ``region``,
89
+ ``city``, ``filter`` (AI IP-quality tier).
90
+ """
91
+ user, pw = resolve_credentials(
92
+ api_key=api_key,
93
+ username=username,
94
+ password=password,
95
+ sub_user=sub_user,
96
+ )
97
+ # Build the targeting username once so a sticky session mints a single sid
98
+ # shared by every request the browser makes.
99
+ gateway_username = build_proxy_username(
100
+ user,
101
+ country=country,
102
+ region=region,
103
+ city=city,
104
+ sticky=sticky,
105
+ filter=filter,
106
+ )
107
+ return gateway_username, pw
108
+
109
+
110
+ async def proxyhat_browser(
111
+ *,
112
+ api_key: str | None = None,
113
+ username: str | None = None,
114
+ password: str | None = None,
115
+ sub_user: str | None = None,
116
+ country: str | None = None,
117
+ region: str | None = None,
118
+ city: str | None = None,
119
+ sticky: bool | str | None = DEFAULT_STICKY,
120
+ filter: str | None = None,
121
+ **start_kwargs: Any,
122
+ ) -> Browser:
123
+ """Start a nodriver Chrome routed through ProxyHat with gateway auth wired.
124
+
125
+ Resolves credentials, calls ``nodriver.start(browser_args=[...], **start_kwargs)``
126
+ with the ``--proxy-server`` flag applied, then enables CDP ``Fetch`` on the
127
+ browser's main tab and registers a ``Fetch.authRequired`` handler that answers
128
+ with the ProxyHat targeting username + sub-user password. Returns the started
129
+ ``Browser`` — navigate with ``await browser.get(url)``.
130
+
131
+ Any extra keyword arguments (``headless``, ``user_data_dir``, ``sandbox``, …)
132
+ are forwarded to ``nodriver.start``; if you pass your own ``browser_args`` they
133
+ are merged with the ProxyHat flag. Sticky by default (one pinned IP for the
134
+ whole session); pass ``sticky=False`` for a rotating IP. See :func:`proxyhat_auth`
135
+ for the full targeting keyword set.
136
+
137
+ ``nodriver`` is imported lazily — install it (``pip install nodriver-proxyhat[nodriver]``)
138
+ to use this helper; :func:`proxyhat_browser_args` and :func:`proxyhat_auth` work
139
+ without it.
140
+ """
141
+ gateway_username, pw = proxyhat_auth(
142
+ api_key=api_key,
143
+ username=username,
144
+ password=password,
145
+ sub_user=sub_user,
146
+ country=country,
147
+ region=region,
148
+ city=city,
149
+ sticky=sticky,
150
+ filter=filter,
151
+ )
152
+
153
+ caller_args = start_kwargs.pop("browser_args", None) or []
154
+ browser_args = proxyhat_browser_args() + list(caller_args)
155
+
156
+ # Imported lazily: nodriver is an optional (peer) dependency, so importing
157
+ # this module never forces a Chrome/nodriver install.
158
+ import nodriver
159
+
160
+ browser = await nodriver.start(browser_args=browser_args, **start_kwargs)
161
+ await enable_proxy_auth(browser.main_tab, gateway_username, pw)
162
+ return browser
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: nodriver-proxyhat
3
+ Version: 0.1.0
4
+ Summary: ProxyHat residential proxies for nodriver (undetected Chrome) — CDP proxy auth, sticky sessions, geo-targeting, rotation.
5
+ Project-URL: Homepage, https://proxyhat.com
6
+ Project-URL: Documentation, https://docs.proxyhat.com
7
+ Project-URL: Repository, https://github.com/ProxyHatCom/nodriver-proxyhat
8
+ Author-email: ProxyHat <support@proxyhat.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: browser-automation,cdp,nodriver,proxy,proxyhat,residential-proxy,sticky-session,undetected-chromedriver,web-scraping
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Internet :: Proxy Servers
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: proxyhat>=0.2.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.4; extra == 'dev'
26
+ Provides-Extra: nodriver
27
+ Requires-Dist: nodriver>=0.32; extra == 'nodriver'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # nodriver-proxyhat
31
+
32
+ Route [nodriver](https://github.com/ultrafunkamsterdam/nodriver) — the successor to undetected-chromedriver — through [ProxyHat](https://proxyhat.com?utm_source=github&utm_medium=readme&utm_campaign=nodriver) residential proxies. **Authenticated** gateway proxies wired straight into nodriver over CDP, plus a sticky residential IP pinned for the whole session, geo-targeting, and rotation.
33
+
34
+ [![CI](https://github.com/ProxyHatCom/nodriver-proxyhat/actions/workflows/ci.yml/badge.svg)](https://github.com/ProxyHatCom/nodriver-proxyhat/actions/workflows/ci.yml)
35
+ [![Compatible with nodriver latest](https://github.com/ProxyHatCom/nodriver-proxyhat/actions/workflows/compat.yml/badge.svg)](https://github.com/ProxyHatCom/nodriver-proxyhat/actions/workflows/compat.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/nodriver-proxyhat)](https://pypi.org/project/nodriver-proxyhat/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
38
+
39
+ ## Why
40
+
41
+ Chrome's `--proxy-server` flag **can't carry a username and password**, so pointing an undetected browser at a credentialed residential gateway normally means a manual CDP dance. And running a real browser from a datacenter IP gets flagged, CAPTCHA-walled, and blocked anyway — exactly what you're using nodriver to avoid.
42
+
43
+ `nodriver-proxyhat` fixes both. It plugs ProxyHat's residential IPs (50M+ across 148+ countries) into nodriver and handles gateway auth the CDP way nodriver is built for: it enables the **Fetch** domain and answers the proxy's `Fetch.authRequired` challenge with your credentials. One pinned residential IP per session by default, so cookies and fingerprint stay consistent while your script works.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install nodriver-proxyhat nodriver
49
+ ```
50
+
51
+ `nodriver` is a peer dependency — bring your own version (and its Chrome). The `proxyhat_browser_args()` / `proxyhat_auth()` helpers work without it; `proxyhat_browser()` needs nodriver installed.
52
+
53
+ ## Quick start
54
+
55
+ ```python
56
+ import nodriver as uc
57
+ from nodriver_proxyhat import proxyhat_browser
58
+
59
+ async def main():
60
+ # An API key (PROXYHAT_API_KEY) auto-selects an active residential sub-user:
61
+ browser = await proxyhat_browser(country="us") # sticky US IP for the whole session
62
+ page = await browser.get("https://httpbin.org/ip")
63
+ print(await page.get_content())
64
+ browser.stop()
65
+
66
+ uc.loop().run_until_complete(main())
67
+ ```
68
+
69
+ Get an API key at [proxyhat.com](https://proxyhat.com?utm_source=github&utm_medium=readme&utm_campaign=nodriver).
70
+
71
+ `proxyhat_browser(...)` calls `nodriver.start(...)` for you and forwards any extra keyword arguments (`headless`, `user_data_dir`, `browser_args`, …) unchanged.
72
+
73
+ ## Credentials
74
+
75
+ Pass them explicitly or via environment variables — options win over env:
76
+
77
+ | Option | Env var | Notes |
78
+ |---|---|---|
79
+ | `api_key` | `PROXYHAT_API_KEY` | Auto-selects an active sub-user with remaining traffic |
80
+ | `sub_user` | `PROXYHAT_SUBUSER` | Pick a specific sub-user by uuid or name (with an API key) |
81
+ | `username` | `PROXYHAT_USERNAME` | Explicit gateway `proxy_username` (skips the API) |
82
+ | `password` | `PROXYHAT_PASSWORD` | Explicit gateway `proxy_password` |
83
+
84
+ ## Targeting
85
+
86
+ ```python
87
+ await proxyhat_browser(
88
+ country="us", # ISO code or "any" (default)
89
+ region="california",
90
+ city="new_york",
91
+ filter="high", # AI IP-quality tier
92
+ sticky="30m", # session lifetime (default); sticky=False rotates every request
93
+ headless=True, # any extra kwarg is forwarded to nodriver.start
94
+ )
95
+ ```
96
+
97
+ The same targeting keyword arguments work on `proxyhat_auth(...)`.
98
+
99
+ ### Sticky IP per session (default)
100
+
101
+ A browser session takes many steps against the same site — logging in, clicking, scrolling. If the exit IP changed mid-session the site would see a user teleporting between cities and block it. So this package is **sticky by default**: one residential IP is pinned for the whole session (`sticky="30m"`, renewed as you work), keeping cookies and fingerprint coherent.
102
+
103
+ Want a fresh IP on **every** request instead (e.g. many independent one-shot fetches)? Turn stickiness off:
104
+
105
+ ```python
106
+ await proxyhat_browser(country="us", sticky=False) # rotating residential IP per connection
107
+ ```
108
+
109
+ Set a custom lifetime with `sticky="2h"`.
110
+
111
+ ## How authentication works
112
+
113
+ nodriver takes the proxy host/port from `--proxy-server=gate.proxyhat.com:8080`, but a residential gateway also needs a username (the ProxyHat targeting string) and password. Since the flag can't carry them, `proxyhat_browser` uses nodriver's raw Chrome DevTools Protocol access instead:
114
+
115
+ 1. it enables the **Fetch** domain on the main tab with `handle_auth_requests=True`;
116
+ 2. it registers a `Fetch.authRequired` handler that answers with your targeting username + sub-user password via `Fetch.continueWithAuth` (`ProvideCredentials`);
117
+ 3. it resumes every other paused request with `Fetch.continueRequest` (enabling Fetch pauses all requests, so non-auth ones must be continued too).
118
+
119
+ The targeting username (e.g. `<user>-country-us-sid-<id>-ttl-30m`) is built by the official [`proxyhat`](https://pypi.org/project/proxyhat/) SDK, so a sticky session mints a single session id shared across the run.
120
+
121
+ This is the **HTTP gateway** (port 8080) — CDP proxy auth answers the HTTP proxy's basic-auth challenge.
122
+
123
+ ## Wiring it yourself
124
+
125
+ Prefer to drive `nodriver.start()` your way? Grab the launch flag and the resolved credentials and wire the CDP handler yourself:
126
+
127
+ ```python
128
+ import nodriver as uc
129
+ from nodriver import cdp
130
+ from nodriver_proxyhat import proxyhat_auth, proxyhat_browser_args, enable_proxy_auth
131
+
132
+ async def main():
133
+ username, password = proxyhat_auth(country="de", sticky="1h")
134
+
135
+ browser = await uc.start(browser_args=proxyhat_browser_args() + ["--headless=new"])
136
+ # enable_proxy_auth does the Fetch.enable + authRequired dance for you:
137
+ await enable_proxy_auth(browser.main_tab, username, password)
138
+
139
+ page = await browser.get("https://httpbin.org/ip")
140
+ print(await page.get_content())
141
+ browser.stop()
142
+
143
+ uc.loop().run_until_complete(main())
144
+ ```
145
+
146
+ `proxyhat_browser_args()` returns `["--proxy-server=gate.proxyhat.com:8080"]`; `proxyhat_auth()` returns the `(username, password)` for a `Fetch.authRequired` handler.
147
+
148
+ ## License
149
+
150
+ MIT © [ProxyHat](https://proxyhat.com)
@@ -0,0 +1,8 @@
1
+ nodriver_proxyhat/__init__.py,sha256=tHzV8AnV96Emh-Iqg3u9Kw6E_zSqu1h4Dum5qwDI2xE,517
2
+ nodriver_proxyhat/_auth.py,sha256=RKd1v968NI6cvKyGUd6qoQxI91uUdsBixCQiXshHPE8,2345
3
+ nodriver_proxyhat/_resolve.py,sha256=tfsq3e743i7zOAMxSXZm1T0l630xAe_OIIqi0zQaHHE,2680
4
+ nodriver_proxyhat/proxy.py,sha256=uDZ_wLrN31MOTJWxe8BifmlfjVGwzSEXR2hkKw60VrA,6364
5
+ nodriver_proxyhat-0.1.0.dist-info/METADATA,sha256=77XI-LaRzcYO7wg6rwEbat-qzLa4_pmvmbxPNJPxyv8,7587
6
+ nodriver_proxyhat-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
+ nodriver_proxyhat-0.1.0.dist-info/licenses/LICENSE,sha256=_yoC1-ij-yJ3LPoEUetu723duo_t9XcI08QcSS6jSiQ,1065
8
+ nodriver_proxyhat-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ProxyHat
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.