dominusnode 1.0.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.
- dominusnode/__init__.py +151 -0
- dominusnode/admin.py +252 -0
- dominusnode/agent_wallet.py +240 -0
- dominusnode/auth.py +271 -0
- dominusnode/client.py +457 -0
- dominusnode/constants.py +18 -0
- dominusnode/errors.py +91 -0
- dominusnode/http_client.py +435 -0
- dominusnode/keys.py +88 -0
- dominusnode/plans.py +93 -0
- dominusnode/proxy.py +248 -0
- dominusnode/py.typed +0 -0
- dominusnode/sessions.py +55 -0
- dominusnode/slots.py +60 -0
- dominusnode/teams.py +339 -0
- dominusnode/token_manager.py +235 -0
- dominusnode/types.py +521 -0
- dominusnode/usage.py +230 -0
- dominusnode/wallet.py +189 -0
- dominusnode/wallet_auth.py +241 -0
- dominusnode/x402.py +88 -0
- dominusnode-1.0.0.dist-info/LICENSE +21 -0
- dominusnode-1.0.0.dist-info/METADATA +13 -0
- dominusnode-1.0.0.dist-info/RECORD +26 -0
- dominusnode-1.0.0.dist-info/WHEEL +5 -0
- dominusnode-1.0.0.dist-info/top_level.txt +1 -0
dominusnode/proxy.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Proxy resource -- build proxy URLs, health, status, and config."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
from .errors import ProxyError
|
|
9
|
+
from .http_client import AsyncHttpClient, SyncHttpClient
|
|
10
|
+
from .types import (
|
|
11
|
+
GeoTargeting,
|
|
12
|
+
ProxyConfig,
|
|
13
|
+
ProxyEndpointConfig,
|
|
14
|
+
ProxyEndpoints,
|
|
15
|
+
ProxyHealth,
|
|
16
|
+
ProxyStatus,
|
|
17
|
+
ProxyUrlOptions,
|
|
18
|
+
ProviderStat,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_proxy_url(
|
|
23
|
+
api_key: str,
|
|
24
|
+
*,
|
|
25
|
+
proxy_host: str,
|
|
26
|
+
http_proxy_port: int,
|
|
27
|
+
socks5_proxy_port: int,
|
|
28
|
+
options: Optional[ProxyUrlOptions] = None,
|
|
29
|
+
) -> str:
|
|
30
|
+
"""Build a proxy URL string for use with HTTP clients.
|
|
31
|
+
|
|
32
|
+
The Dominus Node proxy uses HTTP Basic auth where:
|
|
33
|
+
- username encodes geo-targeting and session options
|
|
34
|
+
- password is the API key
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
api_key: The ``dn_live_...`` API key.
|
|
38
|
+
proxy_host: Hostname of the proxy server.
|
|
39
|
+
http_proxy_port: HTTP proxy port (default 8080).
|
|
40
|
+
socks5_proxy_port: SOCKS5 proxy port (default 1080).
|
|
41
|
+
options: Optional geo-targeting and protocol settings.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A proxy URL like ``http://user:key@host:port``.
|
|
45
|
+
"""
|
|
46
|
+
if not api_key:
|
|
47
|
+
raise ProxyError("API key is required to build proxy URL")
|
|
48
|
+
|
|
49
|
+
# Validate proxy_host to prevent URL injection via crafted hostnames
|
|
50
|
+
if any(c in proxy_host for c in " /\\@?#"):
|
|
51
|
+
raise ValueError("proxy_host contains invalid characters")
|
|
52
|
+
|
|
53
|
+
opts = options or ProxyUrlOptions()
|
|
54
|
+
|
|
55
|
+
# Build username with geo-targeting segments
|
|
56
|
+
username_parts: list[str] = ["user"]
|
|
57
|
+
|
|
58
|
+
if opts.country:
|
|
59
|
+
username_parts.append(f"country-{quote(opts.country.upper(), safe='')}")
|
|
60
|
+
|
|
61
|
+
if opts.state:
|
|
62
|
+
username_parts.append(f"state-{quote(opts.state, safe='')}")
|
|
63
|
+
|
|
64
|
+
if opts.city:
|
|
65
|
+
username_parts.append(f"city-{quote(opts.city, safe='')}")
|
|
66
|
+
|
|
67
|
+
if opts.asn is not None:
|
|
68
|
+
username_parts.append(f"asn-{opts.asn}")
|
|
69
|
+
|
|
70
|
+
if opts.session_id:
|
|
71
|
+
username_parts.append(f"session-{quote(opts.session_id, safe='')}")
|
|
72
|
+
|
|
73
|
+
username = "-".join(username_parts)
|
|
74
|
+
|
|
75
|
+
# Select port based on protocol
|
|
76
|
+
protocol = opts.protocol.lower()
|
|
77
|
+
if protocol == "socks5":
|
|
78
|
+
port = socks5_proxy_port
|
|
79
|
+
scheme = "socks5"
|
|
80
|
+
else:
|
|
81
|
+
port = http_proxy_port
|
|
82
|
+
scheme = "http"
|
|
83
|
+
|
|
84
|
+
# URL-encode password in case of special characters
|
|
85
|
+
encoded_key = quote(api_key, safe="")
|
|
86
|
+
|
|
87
|
+
return f"{scheme}://{username}:{encoded_key}@{proxy_host}:{port}"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _parse_proxy_health(data: dict) -> ProxyHealth:
|
|
91
|
+
return ProxyHealth(
|
|
92
|
+
status=data.get("status", "unknown"),
|
|
93
|
+
active_sessions=data.get("activeSessions", 0),
|
|
94
|
+
uptime_seconds=data.get("uptimeSeconds", 0),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _parse_provider_stat(p: dict) -> ProviderStat:
|
|
99
|
+
return ProviderStat(
|
|
100
|
+
name=p["name"],
|
|
101
|
+
state=p["state"],
|
|
102
|
+
consecutive_failures=p.get("consecutiveFailures", 0),
|
|
103
|
+
total_requests=p.get("totalRequests", 0),
|
|
104
|
+
total_errors=p.get("totalErrors", 0),
|
|
105
|
+
avg_latency_ms=p.get("avgLatencyMs", 0),
|
|
106
|
+
fallback_only=p.get("fallbackOnly", False),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _parse_proxy_status(data: dict) -> ProxyStatus:
|
|
111
|
+
endpoints_data = data.get("endpoints", {})
|
|
112
|
+
endpoints = ProxyEndpoints(
|
|
113
|
+
http=endpoints_data.get("http", ""),
|
|
114
|
+
socks5=endpoints_data.get("socks5", ""),
|
|
115
|
+
)
|
|
116
|
+
providers = [_parse_provider_stat(p) for p in data.get("providers", [])]
|
|
117
|
+
return ProxyStatus(
|
|
118
|
+
status=data.get("status", "unknown"),
|
|
119
|
+
active_sessions=data.get("activeSessions", 0),
|
|
120
|
+
user_active_sessions=data.get("userActiveSessions", 0),
|
|
121
|
+
avg_latency_ms=data.get("avgLatencyMs", 0),
|
|
122
|
+
providers=providers,
|
|
123
|
+
endpoints=endpoints,
|
|
124
|
+
supported_countries=data.get("supportedCountries", []),
|
|
125
|
+
uptime_seconds=data.get("uptimeSeconds", 0),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _parse_proxy_config(data: dict) -> ProxyConfig:
|
|
130
|
+
hp = data.get("httpProxy", {})
|
|
131
|
+
sp = data.get("socks5Proxy", {})
|
|
132
|
+
gt = data.get("geoTargeting", {})
|
|
133
|
+
return ProxyConfig(
|
|
134
|
+
http_proxy=ProxyEndpointConfig(host=hp.get("host", ""), port=hp.get("port", 8080)),
|
|
135
|
+
socks5_proxy=ProxyEndpointConfig(host=sp.get("host", ""), port=sp.get("port", 1080)),
|
|
136
|
+
supported_countries=data.get("supportedCountries", []),
|
|
137
|
+
blocked_countries=data.get("blockedCountries", []),
|
|
138
|
+
max_rotation_interval_minutes=data.get("maxRotationIntervalMinutes", 60),
|
|
139
|
+
min_rotation_interval_minutes=data.get("minRotationIntervalMinutes", 1),
|
|
140
|
+
geo_targeting=GeoTargeting(
|
|
141
|
+
state_support=gt.get("stateSupport", False),
|
|
142
|
+
city_support=gt.get("citySupport", False),
|
|
143
|
+
asn_support=gt.get("asnSupport", False),
|
|
144
|
+
us_states=gt.get("usStates", []),
|
|
145
|
+
major_us_cities=gt.get("majorUsCities", []),
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
151
|
+
# Sync
|
|
152
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ProxyResource:
|
|
156
|
+
"""Synchronous proxy operations."""
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self,
|
|
160
|
+
http: SyncHttpClient,
|
|
161
|
+
*,
|
|
162
|
+
proxy_host: str,
|
|
163
|
+
http_proxy_port: int,
|
|
164
|
+
socks5_proxy_port: int,
|
|
165
|
+
get_api_key: callable,
|
|
166
|
+
) -> None:
|
|
167
|
+
self._http = http
|
|
168
|
+
self._proxy_host = proxy_host
|
|
169
|
+
self._http_proxy_port = http_proxy_port
|
|
170
|
+
self._socks5_proxy_port = socks5_proxy_port
|
|
171
|
+
self._get_api_key = get_api_key
|
|
172
|
+
|
|
173
|
+
def build_url(self, options: Optional[ProxyUrlOptions] = None) -> str:
|
|
174
|
+
"""Build a proxy URL using the current API key and options."""
|
|
175
|
+
api_key = self._get_api_key()
|
|
176
|
+
if not api_key:
|
|
177
|
+
raise ProxyError("No API key set. Call connect_with_key() first.")
|
|
178
|
+
return build_proxy_url(
|
|
179
|
+
api_key,
|
|
180
|
+
proxy_host=self._proxy_host,
|
|
181
|
+
http_proxy_port=self._http_proxy_port,
|
|
182
|
+
socks5_proxy_port=self._socks5_proxy_port,
|
|
183
|
+
options=options,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def get_health(self) -> ProxyHealth:
|
|
187
|
+
"""Get public proxy health status (no auth required)."""
|
|
188
|
+
data = self._http.get("/api/proxy/health")
|
|
189
|
+
return _parse_proxy_health(data)
|
|
190
|
+
|
|
191
|
+
def get_status(self) -> ProxyStatus:
|
|
192
|
+
"""Get detailed proxy status (auth required)."""
|
|
193
|
+
data = self._http.get("/api/proxy/status")
|
|
194
|
+
return _parse_proxy_status(data)
|
|
195
|
+
|
|
196
|
+
def get_config(self) -> ProxyConfig:
|
|
197
|
+
"""Get proxy server configuration (auth required)."""
|
|
198
|
+
data = self._http.get("/api/proxy/config")
|
|
199
|
+
return _parse_proxy_config(data)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
203
|
+
# Async
|
|
204
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class AsyncProxyResource:
|
|
208
|
+
"""Asynchronous proxy operations."""
|
|
209
|
+
|
|
210
|
+
def __init__(
|
|
211
|
+
self,
|
|
212
|
+
http: AsyncHttpClient,
|
|
213
|
+
*,
|
|
214
|
+
proxy_host: str,
|
|
215
|
+
http_proxy_port: int,
|
|
216
|
+
socks5_proxy_port: int,
|
|
217
|
+
get_api_key: callable,
|
|
218
|
+
) -> None:
|
|
219
|
+
self._http = http
|
|
220
|
+
self._proxy_host = proxy_host
|
|
221
|
+
self._http_proxy_port = http_proxy_port
|
|
222
|
+
self._socks5_proxy_port = socks5_proxy_port
|
|
223
|
+
self._get_api_key = get_api_key
|
|
224
|
+
|
|
225
|
+
def build_url(self, options: Optional[ProxyUrlOptions] = None) -> str:
|
|
226
|
+
"""Build a proxy URL (synchronous -- no network call needed)."""
|
|
227
|
+
api_key = self._get_api_key()
|
|
228
|
+
if not api_key:
|
|
229
|
+
raise ProxyError("No API key set. Call connect_with_key() first.")
|
|
230
|
+
return build_proxy_url(
|
|
231
|
+
api_key,
|
|
232
|
+
proxy_host=self._proxy_host,
|
|
233
|
+
http_proxy_port=self._http_proxy_port,
|
|
234
|
+
socks5_proxy_port=self._socks5_proxy_port,
|
|
235
|
+
options=options,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
async def get_health(self) -> ProxyHealth:
|
|
239
|
+
data = await self._http.get("/api/proxy/health")
|
|
240
|
+
return _parse_proxy_health(data)
|
|
241
|
+
|
|
242
|
+
async def get_status(self) -> ProxyStatus:
|
|
243
|
+
data = await self._http.get("/api/proxy/status")
|
|
244
|
+
return _parse_proxy_status(data)
|
|
245
|
+
|
|
246
|
+
async def get_config(self) -> ProxyConfig:
|
|
247
|
+
data = await self._http.get("/api/proxy/config")
|
|
248
|
+
return _parse_proxy_config(data)
|
dominusnode/py.typed
ADDED
|
File without changes
|
dominusnode/sessions.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Sessions resource -- active proxy sessions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from .http_client import AsyncHttpClient, SyncHttpClient
|
|
8
|
+
from .types import ActiveSession
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
12
|
+
# Sync
|
|
13
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SessionsResource:
|
|
17
|
+
"""Synchronous session operations."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, http: SyncHttpClient) -> None:
|
|
20
|
+
self._http = http
|
|
21
|
+
|
|
22
|
+
def get_active(self) -> List[ActiveSession]:
|
|
23
|
+
"""Get currently active proxy sessions."""
|
|
24
|
+
data = self._http.get("/api/sessions/active")
|
|
25
|
+
return [
|
|
26
|
+
ActiveSession(
|
|
27
|
+
id=s["id"],
|
|
28
|
+
started_at=str(s["startedAt"]),
|
|
29
|
+
status=s.get("status", "active"),
|
|
30
|
+
)
|
|
31
|
+
for s in data.get("sessions", [])
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
36
|
+
# Async
|
|
37
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AsyncSessionsResource:
|
|
41
|
+
"""Asynchronous session operations."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, http: AsyncHttpClient) -> None:
|
|
44
|
+
self._http = http
|
|
45
|
+
|
|
46
|
+
async def get_active(self) -> List[ActiveSession]:
|
|
47
|
+
data = await self._http.get("/api/sessions/active")
|
|
48
|
+
return [
|
|
49
|
+
ActiveSession(
|
|
50
|
+
id=s["id"],
|
|
51
|
+
started_at=str(s["startedAt"]),
|
|
52
|
+
status=s.get("status", "active"),
|
|
53
|
+
)
|
|
54
|
+
for s in data.get("sessions", [])
|
|
55
|
+
]
|
dominusnode/slots.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Slots and waitlist resource -- check registration availability and join waitlist."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .http_client import AsyncHttpClient, SyncHttpClient
|
|
6
|
+
from .types import SlotsInfo, WaitlistCount, WaitlistJoinResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SlotsResource:
|
|
10
|
+
"""Synchronous slots and waitlist operations."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, http: SyncHttpClient) -> None:
|
|
13
|
+
self._http = http
|
|
14
|
+
|
|
15
|
+
def get_slots(self) -> SlotsInfo:
|
|
16
|
+
"""Check available registration slots."""
|
|
17
|
+
data = self._http.get("/api/slots", requires_auth=False)
|
|
18
|
+
return SlotsInfo(
|
|
19
|
+
total=data["total"],
|
|
20
|
+
used=data["used"],
|
|
21
|
+
remaining=data["remaining"],
|
|
22
|
+
unlimited=data["unlimited"],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def join_waitlist(self, email: str) -> WaitlistJoinResult:
|
|
26
|
+
"""Join the waitlist when registration slots are full."""
|
|
27
|
+
data = self._http.post("/api/waitlist/join", json={"email": email}, requires_auth=False)
|
|
28
|
+
return WaitlistJoinResult(message=data.get("message", ""))
|
|
29
|
+
|
|
30
|
+
def get_waitlist_count(self) -> WaitlistCount:
|
|
31
|
+
"""Get the number of people on the waitlist."""
|
|
32
|
+
data = self._http.get("/api/waitlist/count", requires_auth=False)
|
|
33
|
+
return WaitlistCount(pending=data["pending"])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncSlotsResource:
|
|
37
|
+
"""Asynchronous slots and waitlist operations."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, http: AsyncHttpClient) -> None:
|
|
40
|
+
self._http = http
|
|
41
|
+
|
|
42
|
+
async def get_slots(self) -> SlotsInfo:
|
|
43
|
+
"""Check available registration slots."""
|
|
44
|
+
data = await self._http.get("/api/slots", requires_auth=False)
|
|
45
|
+
return SlotsInfo(
|
|
46
|
+
total=data["total"],
|
|
47
|
+
used=data["used"],
|
|
48
|
+
remaining=data["remaining"],
|
|
49
|
+
unlimited=data["unlimited"],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def join_waitlist(self, email: str) -> WaitlistJoinResult:
|
|
53
|
+
"""Join the waitlist when registration slots are full."""
|
|
54
|
+
data = await self._http.post("/api/waitlist/join", json={"email": email}, requires_auth=False)
|
|
55
|
+
return WaitlistJoinResult(message=data.get("message", ""))
|
|
56
|
+
|
|
57
|
+
async def get_waitlist_count(self) -> WaitlistCount:
|
|
58
|
+
"""Get the number of people on the waitlist."""
|
|
59
|
+
data = await self._http.get("/api/waitlist/count", requires_auth=False)
|
|
60
|
+
return WaitlistCount(pending=data["pending"])
|