antybrowser 1.0.2__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,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: antybrowser
3
+ Version: 1.0.2
4
+ Summary: Official Antybrowser SDK — Python client for the Antybrowser Local API
5
+ Author: Antybrowser Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://antybrowser.com
8
+ Project-URL: Repository, https://github.com/antybrowser/SDK
9
+ Project-URL: Documentation, https://github.com/antybrowser/SDK/tree/main/py
10
+ Keywords: antybrowser,anti-detect,browser-automation,fingerprint,multi-accounting
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: httpx>=0.25.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pytest-asyncio; extra == "dev"
27
+
28
+ # @antybrowser/sdk (Python)
29
+
30
+ Official Antybrowser Python SDK for the Local API.
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/antybrowser.svg)](https://pypi.org/project/antybrowser/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install antybrowser
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```python
44
+ import asyncio
45
+ from antybrowser import AntybrowserClient, CreateProfileRequest
46
+
47
+ async def main():
48
+ async with AntybrowserClient(api_key="your_key", port=5173) as client:
49
+ # List profiles
50
+ profiles = await client.get_profiles()
51
+ for p in profiles:
52
+ print(f"{p.name} (status: {p.status})")
53
+
54
+ # Create a profile
55
+ profile = await client.create_profile(CreateProfileRequest(
56
+ name="My Profile",
57
+ browser_type="Chrome",
58
+ os_fingerprint="Windows",
59
+ language="en-US",
60
+ use_fingerprint=True,
61
+ ))
62
+
63
+ # Start it — get debug port for Selenium/Playwright
64
+ result = await client.start_profile(profile.id)
65
+ print(f"Debug port: {result.data.debug_port}")
66
+
67
+ asyncio.run(main())
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ ```python
73
+ # Simple
74
+ client = AntybrowserClient(api_key="my_key")
75
+
76
+ # Custom port
77
+ client = AntybrowserClient(api_key="my_key", port=5174)
78
+
79
+ # Full override
80
+ client = AntybrowserClient(api_key="my_key", base_url="http://10.0.0.5:5173")
81
+ ```
82
+
83
+ ## Context Manager
84
+
85
+ ```python
86
+ async with AntybrowserClient(api_key="my_key") as client:
87
+ profiles = await client.get_profiles()
88
+ # Connection automatically closed
89
+ ```
90
+
91
+ ## API Methods
92
+
93
+ | Method | Description |
94
+ |--------|-------------|
95
+ | `get_status()` | Check API connection |
96
+ | `get_settings()` | Get settings |
97
+ | `get_sync_status()` | Get sync status |
98
+ | `refresh_sync(profile_id?)` | Trigger sync |
99
+ | `get_profiles()` | List profiles |
100
+ | `create_profile(request)` | Create profile |
101
+ | `update_profile(id, **fields)` | Update profile |
102
+ | `delete_profile(id)` | Delete profile |
103
+ | `start_profile(id)` | Start profile |
104
+ | `stop_profile(id)` | Stop profile |
105
+ | `duplicate_profile(id, opts?)` | Duplicate profile |
106
+ | `get_automations()` | List automations |
107
+ | `run_automation(id, request)` | Run automation |
108
+ | `get_groups()` | List groups |
109
+ | `create_group(request)` | Create group |
110
+ | `update_group(id, **fields)` | Update group |
111
+ | `delete_group(id)` | Delete group |
112
+ | `get_proxies()` | List proxies |
113
+ | `create_proxy(request)` | Create proxy |
114
+ | `check_proxy(host, port, ...)` | Check proxy |
115
+ | `check_proxies_bulk(proxies)` | Bulk check |
116
+ | `delete_proxy(id)` | Delete proxy |
117
+ | `get_extensions()` | List extensions |
118
+ | `delete_extension(id)` | Delete extension |
119
+ | `get_profile_extensions(id)` | Get profile extensions |
120
+ | `set_profile_extensions(id, ids)` | Set profile extensions |
121
+
122
+ ## Error Handling
123
+
124
+ ```python
125
+ from antybrowser import AntybrowserError
126
+
127
+ try:
128
+ await client.get_profiles()
129
+ except AntybrowserError as e:
130
+ print(f"Status: {e.status_code}")
131
+ print(f"Body: {e.response_body}")
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
@@ -0,0 +1,109 @@
1
+ # @antybrowser/sdk (Python)
2
+
3
+ Official Antybrowser Python SDK for the Local API.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/antybrowser.svg)](https://pypi.org/project/antybrowser/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install antybrowser
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```python
17
+ import asyncio
18
+ from antybrowser import AntybrowserClient, CreateProfileRequest
19
+
20
+ async def main():
21
+ async with AntybrowserClient(api_key="your_key", port=5173) as client:
22
+ # List profiles
23
+ profiles = await client.get_profiles()
24
+ for p in profiles:
25
+ print(f"{p.name} (status: {p.status})")
26
+
27
+ # Create a profile
28
+ profile = await client.create_profile(CreateProfileRequest(
29
+ name="My Profile",
30
+ browser_type="Chrome",
31
+ os_fingerprint="Windows",
32
+ language="en-US",
33
+ use_fingerprint=True,
34
+ ))
35
+
36
+ # Start it — get debug port for Selenium/Playwright
37
+ result = await client.start_profile(profile.id)
38
+ print(f"Debug port: {result.data.debug_port}")
39
+
40
+ asyncio.run(main())
41
+ ```
42
+
43
+ ## Configuration
44
+
45
+ ```python
46
+ # Simple
47
+ client = AntybrowserClient(api_key="my_key")
48
+
49
+ # Custom port
50
+ client = AntybrowserClient(api_key="my_key", port=5174)
51
+
52
+ # Full override
53
+ client = AntybrowserClient(api_key="my_key", base_url="http://10.0.0.5:5173")
54
+ ```
55
+
56
+ ## Context Manager
57
+
58
+ ```python
59
+ async with AntybrowserClient(api_key="my_key") as client:
60
+ profiles = await client.get_profiles()
61
+ # Connection automatically closed
62
+ ```
63
+
64
+ ## API Methods
65
+
66
+ | Method | Description |
67
+ |--------|-------------|
68
+ | `get_status()` | Check API connection |
69
+ | `get_settings()` | Get settings |
70
+ | `get_sync_status()` | Get sync status |
71
+ | `refresh_sync(profile_id?)` | Trigger sync |
72
+ | `get_profiles()` | List profiles |
73
+ | `create_profile(request)` | Create profile |
74
+ | `update_profile(id, **fields)` | Update profile |
75
+ | `delete_profile(id)` | Delete profile |
76
+ | `start_profile(id)` | Start profile |
77
+ | `stop_profile(id)` | Stop profile |
78
+ | `duplicate_profile(id, opts?)` | Duplicate profile |
79
+ | `get_automations()` | List automations |
80
+ | `run_automation(id, request)` | Run automation |
81
+ | `get_groups()` | List groups |
82
+ | `create_group(request)` | Create group |
83
+ | `update_group(id, **fields)` | Update group |
84
+ | `delete_group(id)` | Delete group |
85
+ | `get_proxies()` | List proxies |
86
+ | `create_proxy(request)` | Create proxy |
87
+ | `check_proxy(host, port, ...)` | Check proxy |
88
+ | `check_proxies_bulk(proxies)` | Bulk check |
89
+ | `delete_proxy(id)` | Delete proxy |
90
+ | `get_extensions()` | List extensions |
91
+ | `delete_extension(id)` | Delete extension |
92
+ | `get_profile_extensions(id)` | Get profile extensions |
93
+ | `set_profile_extensions(id, ids)` | Set profile extensions |
94
+
95
+ ## Error Handling
96
+
97
+ ```python
98
+ from antybrowser import AntybrowserError
99
+
100
+ try:
101
+ await client.get_profiles()
102
+ except AntybrowserError as e:
103
+ print(f"Status: {e.status_code}")
104
+ print(f"Body: {e.response_body}")
105
+ ```
106
+
107
+ ## License
108
+
109
+ MIT
@@ -0,0 +1,44 @@
1
+ """Official Antybrowser SDK — Python client for the Antybrowser Local API."""
2
+
3
+ from antybrowser.client import AntybrowserClient
4
+ from antybrowser.errors import AntybrowserError
5
+ from antybrowser.types import (
6
+ Profile,
7
+ CreateProfileRequest,
8
+ Proxy,
9
+ CreateProxyRequest,
10
+ ProxyCheckResult,
11
+ Group,
12
+ CreateGroupRequest,
13
+ Extension,
14
+ Automation,
15
+ RunAutomationRequest,
16
+ RunAutomationResult,
17
+ Settings,
18
+ SyncStatus,
19
+ StatusResponse,
20
+ StartProfileResponse,
21
+ DuplicateProfileRequest,
22
+ )
23
+
24
+ __version__ = "1.0.2"
25
+ __all__ = [
26
+ "AntybrowserClient",
27
+ "AntybrowserError",
28
+ "Profile",
29
+ "CreateProfileRequest",
30
+ "Proxy",
31
+ "CreateProxyRequest",
32
+ "ProxyCheckResult",
33
+ "Group",
34
+ "CreateGroupRequest",
35
+ "Extension",
36
+ "Automation",
37
+ "RunAutomationRequest",
38
+ "RunAutomationResult",
39
+ "Settings",
40
+ "SyncStatus",
41
+ "StatusResponse",
42
+ "StartProfileResponse",
43
+ "DuplicateProfileRequest",
44
+ ]
@@ -0,0 +1,268 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ import httpx
6
+
7
+ from antybrowser.errors import AntybrowserError
8
+ from antybrowser.types import (
9
+ Automation,
10
+ CreateGroupRequest,
11
+ CreateProfileRequest,
12
+ CreateProxyRequest,
13
+ Extension,
14
+ Group,
15
+ Profile,
16
+ Proxy,
17
+ ProxyCheckResult,
18
+ RunAutomationRequest,
19
+ RunAutomationResult,
20
+ Settings,
21
+ StartProfileData,
22
+ StartProfileResponse,
23
+ StatusResponse,
24
+ SyncStatus,
25
+ DuplicateProfileRequest,
26
+ )
27
+
28
+
29
+ class AntybrowserClient:
30
+ """Client for the Antybrowser Local API.
31
+
32
+ Args:
33
+ api_key: Your Antybrowser API key.
34
+ port: Local API port (default 5173).
35
+ base_url: Full base URL override (overrides port).
36
+ timeout: Request timeout in seconds (default 30).
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ api_key: str,
42
+ port: int = 5173,
43
+ base_url: Optional[str] = None,
44
+ timeout: float = 30.0,
45
+ ):
46
+ self._api_key = api_key
47
+ self._base_url = base_url or f"http://127.0.0.1:{port}"
48
+ self._client = httpx.AsyncClient(
49
+ base_url=self._base_url,
50
+ headers={"x-api-key": api_key, "Content-Type": "application/json"},
51
+ timeout=timeout,
52
+ )
53
+
54
+ async def close(self) -> None:
55
+ await self._client.aclose()
56
+
57
+ async def __aenter__(self) -> AntybrowserClient:
58
+ return self
59
+
60
+ async def __aexit__(self, *args: Any) -> None:
61
+ await self.close()
62
+
63
+ # ─── System ──────────────────────────────────────────────────────────
64
+
65
+ async def get_status(self) -> StatusResponse:
66
+ data = await self._get("/api/status")
67
+ return StatusResponse(**data)
68
+
69
+ async def get_settings(self) -> Settings:
70
+ data = await self._get("/api/settings")
71
+ return Settings.from_dict(data)
72
+
73
+ async def get_sync_status(self) -> SyncStatus:
74
+ data = await self._get("/api/sync/status")
75
+ return SyncStatus.from_dict(data)
76
+
77
+ async def refresh_sync(self, profile_id: Optional[int] = None) -> Dict[str, Any]:
78
+ body = {"profileId": profile_id} if profile_id is not None else {}
79
+ return await self._post("/api/sync/refresh", body)
80
+
81
+ # ─── Profiles ────────────────────────────────────────────────────────
82
+
83
+ async def get_profiles(self) -> List[Profile]:
84
+ data = await self._get("/api/profiles")
85
+ return [Profile.from_dict(p) for p in data]
86
+
87
+ async def create_profile(self, request: CreateProfileRequest) -> Profile:
88
+ data = await self._post("/api/profiles", request.to_dict())
89
+ return Profile.from_dict(data)
90
+
91
+ async def update_profile(self, profile_id: int, **kwargs: Any) -> Profile:
92
+ data = await self._put(f"/api/profiles/{profile_id}", kwargs)
93
+ return Profile.from_dict(data)
94
+
95
+ async def delete_profile(self, profile_id: int) -> Dict[str, Any]:
96
+ return await self._delete(f"/api/profiles/{profile_id}")
97
+
98
+ async def start_profile(self, profile_id: int) -> StartProfileResponse:
99
+ data = await self._post(f"/api/profiles/{profile_id}/start")
100
+ return StartProfileResponse(
101
+ success=data.get("success", False),
102
+ data=StartProfileData(
103
+ debug_port=data.get("data", {}).get("debugPort"),
104
+ extra={k: v for k, v in data.get("data", {}).items() if k != "debugPort"},
105
+ ),
106
+ )
107
+
108
+ async def stop_profile(self, profile_id: int) -> Dict[str, Any]:
109
+ return await self._post(f"/api/profiles/{profile_id}/stop")
110
+
111
+ async def duplicate_profile(
112
+ self, profile_id: int, options: Optional[DuplicateProfileRequest] = None
113
+ ) -> Profile:
114
+ body = options.to_dict() if options else {}
115
+ data = await self._post(f"/api/profiles/{profile_id}/duplicate", body)
116
+ return Profile.from_dict(data)
117
+
118
+ # ─── Automations ─────────────────────────────────────────────────────
119
+
120
+ async def get_automations(self) -> List[Automation]:
121
+ data = await self._get("/api/automations")
122
+ return [Automation.from_dict(a) for a in data]
123
+
124
+ async def run_automation(
125
+ self, automation_id: int, request: RunAutomationRequest
126
+ ) -> RunAutomationResult:
127
+ data = await self._post(f"/api/automations/{automation_id}/run", request.to_dict())
128
+ return RunAutomationResult(
129
+ success=data.get("success", False),
130
+ message=data.get("message", ""),
131
+ variables=data.get("variables"),
132
+ )
133
+
134
+ # ─── Groups ──────────────────────────────────────────────────────────
135
+
136
+ async def get_groups(self) -> List[Group]:
137
+ data = await self._get("/api/groups")
138
+ return [Group.from_dict(g) for g in data]
139
+
140
+ async def create_group(self, request: CreateGroupRequest) -> Group:
141
+ data = await self._post("/api/groups", request.to_dict())
142
+ return Group.from_dict(data)
143
+
144
+ async def update_group(self, group_id: int, **kwargs: Any) -> Group:
145
+ data = await self._put(f"/api/groups/{group_id}", kwargs)
146
+ return Group.from_dict(data)
147
+
148
+ async def delete_group(self, group_id: int) -> None:
149
+ await self._delete(f"/api/groups/{group_id}")
150
+
151
+ # ─── Proxies ─────────────────────────────────────────────────────────
152
+
153
+ async def get_proxies(self) -> List[Proxy]:
154
+ data = await self._get("/api/proxies")
155
+ return [Proxy.from_dict(p) for p in data]
156
+
157
+ async def create_proxy(self, request: CreateProxyRequest) -> Proxy:
158
+ data = await self._post("/api/proxies", request.to_dict())
159
+ return Proxy.from_dict(data)
160
+
161
+ async def check_proxy(
162
+ self,
163
+ host: str,
164
+ port: int,
165
+ username: Optional[str] = None,
166
+ password: Optional[str] = None,
167
+ type: Optional[str] = None,
168
+ ) -> ProxyCheckResult:
169
+ body: Dict[str, Any] = {"host": host, "port": port}
170
+ if username:
171
+ body["username"] = username
172
+ if password:
173
+ body["password"] = password
174
+ if type:
175
+ body["type"] = type
176
+ data = await self._post("/api/proxies/check", body)
177
+ return ProxyCheckResult(
178
+ success=data.get("success", False),
179
+ details=data.get("details"),
180
+ error_message=data.get("errorMessage"),
181
+ )
182
+
183
+ async def check_proxies_bulk(
184
+ self, proxies: List[Union[str, Dict[str, Any]]]
185
+ ) -> List[ProxyCheckResult]:
186
+ data = await self._post("/api/proxies/check-bulk", {"proxies": proxies})
187
+ results = data.get("results", data) if isinstance(data, dict) else data
188
+ return [
189
+ ProxyCheckResult(
190
+ success=r.get("success", False),
191
+ details=r.get("details"),
192
+ error_message=r.get("errorMessage"),
193
+ )
194
+ for r in results
195
+ ]
196
+
197
+ async def delete_proxy(self, proxy_id: int) -> None:
198
+ await self._delete(f"/api/proxies/{proxy_id}")
199
+
200
+ # ─── Extensions ──────────────────────────────────────────────────────
201
+
202
+ async def get_extensions(self) -> List[Extension]:
203
+ data = await self._get("/api/extensions")
204
+ return [Extension.from_dict(e) for e in data]
205
+
206
+ async def delete_extension(self, extension_id: int) -> None:
207
+ await self._delete(f"/api/extensions/{extension_id}")
208
+
209
+ async def get_profile_extensions(
210
+ self, profile_id: int, details: bool = False
211
+ ) -> List[Extension]:
212
+ data = await self._get(f"/api/profiles/{profile_id}/extensions?details={str(details).lower()}")
213
+ return [Extension.from_dict(e) for e in data]
214
+
215
+ async def set_profile_extensions(
216
+ self, profile_id: int, extension_ids: List[int]
217
+ ) -> Dict[str, Any]:
218
+ return await self._post(
219
+ f"/api/profiles/{profile_id}/extensions", {"extensionIds": extension_ids}
220
+ )
221
+
222
+ # ─── HTTP Helpers ────────────────────────────────────────────────────
223
+
224
+ async def _get(self, path: str) -> Any:
225
+ try:
226
+ resp = await self._client.get(path)
227
+ except httpx.HTTPError as e:
228
+ raise AntybrowserError(f"Failed to connect to Antybrowser: {e}")
229
+ return self._handle(resp)
230
+
231
+ async def _post(self, path: str, body: Optional[Dict[str, Any]] = None) -> Any:
232
+ try:
233
+ resp = await self._client.post(path, json=body or {})
234
+ except httpx.HTTPError as e:
235
+ raise AntybrowserError(f"Failed to connect to Antybrowser: {e}")
236
+ return self._handle(resp)
237
+
238
+ async def _put(self, path: str, body: Dict[str, Any]) -> Any:
239
+ try:
240
+ resp = await self._client.put(path, json=body)
241
+ except httpx.HTTPError as e:
242
+ raise AntybrowserError(f"Failed to connect to Antybrowser: {e}")
243
+ return self._handle(resp)
244
+
245
+ async def _delete(self, path: str) -> Any:
246
+ try:
247
+ resp = await self._client.delete(path)
248
+ except httpx.HTTPError as e:
249
+ raise AntybrowserError(f"Failed to connect to Antybrowser: {e}")
250
+ return self._handle(resp)
251
+
252
+ def _handle(self, resp: httpx.Response) -> Any:
253
+ if not resp.is_success:
254
+ raise AntybrowserError(
255
+ f"API request failed with status {resp.status_code}",
256
+ status_code=resp.status_code,
257
+ response_body=resp.text,
258
+ )
259
+ if not resp.text or not resp.text.strip():
260
+ return {}
261
+ try:
262
+ return resp.json()
263
+ except Exception:
264
+ raise AntybrowserError(
265
+ "Invalid JSON response from API",
266
+ status_code=resp.status_code,
267
+ response_body=resp.text,
268
+ )
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class AntybrowserError(Exception):
7
+ """Raised when the Antybrowser API returns an error."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ status_code: Optional[int] = None,
13
+ response_body: Optional[str] = None,
14
+ ):
15
+ super().__init__(message)
16
+ self.status_code = status_code
17
+ self.response_body = response_body
@@ -0,0 +1 @@
1
+ """Py.typed marker for PEP 561 compliance."""
@@ -0,0 +1,347 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Dict, List, Optional, Union
5
+
6
+
7
+ @dataclass
8
+ class StatusResponse:
9
+ success: bool
10
+ status: str
11
+ version: str
12
+
13
+
14
+ @dataclass
15
+ class StartProfileData:
16
+ debug_port: Optional[int] = None
17
+ extra: Dict[str, Any] = field(default_factory=dict)
18
+
19
+ def __getattr__(self, name: str) -> Any:
20
+ return self.extra.get(name)
21
+
22
+
23
+ @dataclass
24
+ class StartProfileResponse:
25
+ success: bool
26
+ data: StartProfileData
27
+
28
+
29
+ @dataclass
30
+ class Profile:
31
+ id: int
32
+ name: str
33
+ directory_name: Optional[str] = None
34
+ group_id: Optional[int] = None
35
+ proxy_id: Optional[int] = None
36
+ browser_type: Optional[str] = None
37
+ browser_version: Optional[str] = None
38
+ os_fingerprint: Optional[str] = None
39
+ screen_resolution: Optional[str] = None
40
+ language: Optional[str] = None
41
+ accept_language: Optional[str] = None
42
+ timezone: Optional[str] = None
43
+ use_fingerprint: Optional[bool] = None
44
+ fingerprint_id: Optional[str] = None
45
+ restore_session: Optional[bool] = None
46
+ low_bandwidth: Optional[bool] = None
47
+ notes: Optional[str] = None
48
+ start_url: Optional[str] = None
49
+ custom_flags: Optional[str] = None
50
+ status: Optional[str] = None
51
+ needs_sync: Optional[bool] = None
52
+ last_pid: Optional[int] = None
53
+ debug_port: Optional[int] = None
54
+ created_at: Optional[str] = None
55
+ updated_at: Optional[str] = None
56
+ last_synced_at: Optional[str] = None
57
+ s3_key: Optional[str] = None
58
+ trash: Optional[bool] = None
59
+ deleted_at: Optional[str] = None
60
+ hidden: Optional[bool] = None
61
+ extra: Dict[str, Any] = field(default_factory=dict)
62
+
63
+ @classmethod
64
+ def from_dict(cls, d: Dict[str, Any]) -> Profile:
65
+ known = {f.name for f in cls.__dataclass_fields__.values()} | {"extra"}
66
+ extra = {k: v for k, v in d.items() if k not in known}
67
+ return cls(
68
+ id=d["id"],
69
+ name=d["name"],
70
+ directory_name=d.get("directoryName"),
71
+ group_id=d.get("groupId"),
72
+ proxy_id=d.get("proxyId"),
73
+ browser_type=d.get("browserType"),
74
+ browser_version=d.get("browserVersion"),
75
+ os_fingerprint=d.get("osFingerprint"),
76
+ screen_resolution=d.get("screenResolution"),
77
+ language=d.get("language"),
78
+ accept_language=d.get("acceptLanguage"),
79
+ timezone=d.get("timezone"),
80
+ use_fingerprint=d.get("useFingerprint"),
81
+ fingerprint_id=d.get("fingerprintId"),
82
+ restore_session=d.get("restoreSession"),
83
+ low_bandwidth=d.get("lowBandwidth"),
84
+ notes=d.get("notes"),
85
+ start_url=d.get("startUrl"),
86
+ custom_flags=d.get("customFlags"),
87
+ status=d.get("status"),
88
+ needs_sync=d.get("needsSync"),
89
+ last_pid=d.get("lastPid"),
90
+ debug_port=d.get("debugPort"),
91
+ created_at=d.get("createdAt"),
92
+ updated_at=d.get("updatedAt"),
93
+ last_synced_at=d.get("lastSyncedAt"),
94
+ s3_key=d.get("s3Key"),
95
+ trash=d.get("trash"),
96
+ deleted_at=d.get("deletedAt"),
97
+ hidden=d.get("hidden"),
98
+ extra=extra,
99
+ )
100
+
101
+
102
+ @dataclass
103
+ class CreateProfileRequest:
104
+ name: str
105
+ directory_name: Optional[str] = None
106
+ group_id: Optional[int] = None
107
+ proxy_id: Optional[int] = None
108
+ browser_type: Optional[str] = None
109
+ os_fingerprint: Optional[str] = None
110
+ screen_resolution: Optional[str] = None
111
+ language: Optional[str] = None
112
+ accept_language: Optional[str] = None
113
+ timezone: Optional[str] = None
114
+ use_fingerprint: Optional[bool] = None
115
+ fingerprint_id: Optional[str] = None
116
+ restore_session: Optional[bool] = None
117
+ low_bandwidth: Optional[bool] = None
118
+ notes: Optional[str] = None
119
+ start_url: Optional[str] = None
120
+ custom_flags: Optional[str] = None
121
+
122
+ def to_dict(self) -> Dict[str, Any]:
123
+ d: Dict[str, Any] = {"name": self.name}
124
+ mapping = {
125
+ "directory_name": "directoryName", "group_id": "groupId",
126
+ "proxy_id": "proxyId", "browser_type": "browserType",
127
+ "os_fingerprint": "osFingerprint", "screen_resolution": "screenResolution",
128
+ "accept_language": "acceptLanguage", "use_fingerprint": "useFingerprint",
129
+ "fingerprint_id": "fingerprintId", "restore_session": "restoreSession",
130
+ "low_bandwidth": "lowBandwidth", "start_url": "startUrl",
131
+ "custom_flags": "customFlags",
132
+ }
133
+ for py_key, api_key in mapping.items():
134
+ val = getattr(self, py_key)
135
+ if val is not None:
136
+ d[api_key] = val
137
+ for attr in ("language", "timezone", "notes"):
138
+ val = getattr(self, attr)
139
+ if val is not None:
140
+ d[attr] = val
141
+ return d
142
+
143
+
144
+ @dataclass
145
+ class Proxy:
146
+ id: int
147
+ name: Optional[str] = None
148
+ type: Optional[str] = None
149
+ host: Optional[str] = None
150
+ port: Optional[int] = None
151
+ username: Optional[str] = None
152
+ password: Optional[str] = None
153
+ status: Optional[str] = None
154
+ country_code: Optional[str] = None
155
+ ip: Optional[str] = None
156
+ country: Optional[str] = None
157
+ timezone: Optional[str] = None
158
+ asn: Optional[str] = None
159
+ isp: Optional[str] = None
160
+ extra: Dict[str, Any] = field(default_factory=dict)
161
+
162
+ @classmethod
163
+ def from_dict(cls, d: Dict[str, Any]) -> Proxy:
164
+ known = {f.name for f in cls.__dataclass_fields__.values()} | {"extra"}
165
+ extra = {k: v for k, v in d.items() if k not in known}
166
+ return cls(
167
+ id=d["id"], name=d.get("name"), type=d.get("type"),
168
+ host=d.get("host"), port=d.get("port"),
169
+ username=d.get("username"), password=d.get("password"),
170
+ status=d.get("status"), country_code=d.get("countryCode"),
171
+ ip=d.get("ip"), country=d.get("country"),
172
+ timezone=d.get("timezone"), asn=d.get("asn"), isp=d.get("isp"),
173
+ extra=extra,
174
+ )
175
+
176
+
177
+ @dataclass
178
+ class CreateProxyRequest:
179
+ name: str
180
+ host: str
181
+ port: int
182
+ type: str
183
+ username: Optional[str] = None
184
+ password: Optional[str] = None
185
+
186
+ def to_dict(self) -> Dict[str, Any]:
187
+ d: Dict[str, Any] = {"name": self.name, "host": self.host, "port": self.port, "type": self.type}
188
+ if self.username is not None:
189
+ d["username"] = self.username
190
+ if self.password is not None:
191
+ d["password"] = self.password
192
+ return d
193
+
194
+
195
+ @dataclass
196
+ class ProxyCheckResult:
197
+ success: bool
198
+ details: Optional[Dict[str, Any]] = None
199
+ error_message: Optional[str] = None
200
+
201
+
202
+ @dataclass
203
+ class Group:
204
+ id: int
205
+ name: str
206
+ description: Optional[str] = None
207
+ color: Optional[str] = None
208
+ display_order: Optional[int] = None
209
+ created_at: Optional[str] = None
210
+ updated_at: Optional[str] = None
211
+ extra: Dict[str, Any] = field(default_factory=dict)
212
+
213
+ @classmethod
214
+ def from_dict(cls, d: Dict[str, Any]) -> Group:
215
+ return cls(
216
+ id=d["id"], name=d["name"], description=d.get("description"),
217
+ color=d.get("color"), display_order=d.get("displayOrder"),
218
+ created_at=d.get("createdAt"), updated_at=d.get("updatedAt"),
219
+ )
220
+
221
+
222
+ @dataclass
223
+ class CreateGroupRequest:
224
+ name: str
225
+ description: Optional[str] = None
226
+ color: Optional[str] = None
227
+
228
+ def to_dict(self) -> Dict[str, Any]:
229
+ d: Dict[str, Any] = {"name": self.name}
230
+ if self.description is not None:
231
+ d["description"] = self.description
232
+ if self.color is not None:
233
+ d["color"] = self.color
234
+ return d
235
+
236
+
237
+ @dataclass
238
+ class Extension:
239
+ id: int
240
+ name: str
241
+ path: Optional[str] = None
242
+ description: Optional[str] = None
243
+ icon: Optional[str] = None
244
+ icon_data_url: Optional[str] = None
245
+ created_at: Optional[str] = None
246
+ extra: Dict[str, Any] = field(default_factory=dict)
247
+
248
+ @classmethod
249
+ def from_dict(cls, d: Dict[str, Any]) -> Extension:
250
+ return cls(
251
+ id=d["id"], name=d["name"], path=d.get("path"),
252
+ description=d.get("description"), icon=d.get("icon"),
253
+ icon_data_url=d.get("iconDataUrl"), created_at=d.get("createdAt"),
254
+ )
255
+
256
+
257
+ @dataclass
258
+ class Automation:
259
+ id: int
260
+ name: str
261
+ description: Optional[str] = None
262
+ status: Optional[str] = None
263
+ last_run: Optional[str] = None
264
+ created_at: Optional[str] = None
265
+ updated_at: Optional[str] = None
266
+ extra: Dict[str, Any] = field(default_factory=dict)
267
+
268
+ @classmethod
269
+ def from_dict(cls, d: Dict[str, Any]) -> Automation:
270
+ return cls(
271
+ id=d["id"], name=d["name"], description=d.get("description"),
272
+ status=d.get("status"), last_run=d.get("lastRun"),
273
+ created_at=d.get("createdAt"), updated_at=d.get("updatedAt"),
274
+ )
275
+
276
+
277
+ @dataclass
278
+ class RunAutomationRequest:
279
+ profile_id: int
280
+ delete_cookies: Optional[bool] = None
281
+ variables: Optional[Dict[str, str]] = None
282
+
283
+ def to_dict(self) -> Dict[str, Any]:
284
+ d: Dict[str, Any] = {"profileId": self.profile_id}
285
+ if self.delete_cookies is not None:
286
+ d["deleteCookies"] = self.delete_cookies
287
+ if self.variables is not None:
288
+ d["variables"] = self.variables
289
+ return d
290
+
291
+
292
+ @dataclass
293
+ class RunAutomationResult:
294
+ success: bool
295
+ message: str
296
+ variables: Optional[Dict[str, Any]] = None
297
+
298
+
299
+ @dataclass
300
+ class Settings:
301
+ id: Optional[int] = None
302
+ chrome_path: Optional[str] = None
303
+ api_key: Optional[str] = None
304
+ language: Optional[str] = None
305
+ extra: Dict[str, Any] = field(default_factory=dict)
306
+
307
+ @classmethod
308
+ def from_dict(cls, d: Dict[str, Any]) -> Settings:
309
+ return cls(
310
+ id=d.get("id"), chrome_path=d.get("chromePath"),
311
+ api_key=d.get("apiKey"), language=d.get("language"),
312
+ extra={k: v for k, v in d.items() if k not in ("id", "chromePath", "apiKey", "language")},
313
+ )
314
+
315
+
316
+ @dataclass
317
+ class SyncStatus:
318
+ total: int
319
+ completed: int
320
+ is_syncing: bool
321
+ active: List[Any] = field(default_factory=list)
322
+ active_extensions: List[Any] = field(default_factory=list)
323
+ errors: Dict[str, Any] = field(default_factory=dict)
324
+ progress: Dict[str, Any] = field(default_factory=dict)
325
+
326
+ @classmethod
327
+ def from_dict(cls, d: Dict[str, Any]) -> SyncStatus:
328
+ return cls(
329
+ total=d.get("total", 0), completed=d.get("completed", 0),
330
+ is_syncing=d.get("isSyncing", False), active=d.get("active", []),
331
+ active_extensions=d.get("activeExtensions", []),
332
+ errors=d.get("errors", {}), progress=d.get("progress", {}),
333
+ )
334
+
335
+
336
+ @dataclass
337
+ class DuplicateProfileRequest:
338
+ name: Optional[str] = None
339
+ directory_name: Optional[str] = None
340
+
341
+ def to_dict(self) -> Dict[str, Any]:
342
+ d: Dict[str, Any] = {}
343
+ if self.name is not None:
344
+ d["name"] = self.name
345
+ if self.directory_name is not None:
346
+ d["directoryName"] = self.directory_name
347
+ return d
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: antybrowser
3
+ Version: 1.0.2
4
+ Summary: Official Antybrowser SDK — Python client for the Antybrowser Local API
5
+ Author: Antybrowser Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://antybrowser.com
8
+ Project-URL: Repository, https://github.com/antybrowser/SDK
9
+ Project-URL: Documentation, https://github.com/antybrowser/SDK/tree/main/py
10
+ Keywords: antybrowser,anti-detect,browser-automation,fingerprint,multi-accounting
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: httpx>=0.25.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pytest-asyncio; extra == "dev"
27
+
28
+ # @antybrowser/sdk (Python)
29
+
30
+ Official Antybrowser Python SDK for the Local API.
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/antybrowser.svg)](https://pypi.org/project/antybrowser/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install antybrowser
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```python
44
+ import asyncio
45
+ from antybrowser import AntybrowserClient, CreateProfileRequest
46
+
47
+ async def main():
48
+ async with AntybrowserClient(api_key="your_key", port=5173) as client:
49
+ # List profiles
50
+ profiles = await client.get_profiles()
51
+ for p in profiles:
52
+ print(f"{p.name} (status: {p.status})")
53
+
54
+ # Create a profile
55
+ profile = await client.create_profile(CreateProfileRequest(
56
+ name="My Profile",
57
+ browser_type="Chrome",
58
+ os_fingerprint="Windows",
59
+ language="en-US",
60
+ use_fingerprint=True,
61
+ ))
62
+
63
+ # Start it — get debug port for Selenium/Playwright
64
+ result = await client.start_profile(profile.id)
65
+ print(f"Debug port: {result.data.debug_port}")
66
+
67
+ asyncio.run(main())
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ ```python
73
+ # Simple
74
+ client = AntybrowserClient(api_key="my_key")
75
+
76
+ # Custom port
77
+ client = AntybrowserClient(api_key="my_key", port=5174)
78
+
79
+ # Full override
80
+ client = AntybrowserClient(api_key="my_key", base_url="http://10.0.0.5:5173")
81
+ ```
82
+
83
+ ## Context Manager
84
+
85
+ ```python
86
+ async with AntybrowserClient(api_key="my_key") as client:
87
+ profiles = await client.get_profiles()
88
+ # Connection automatically closed
89
+ ```
90
+
91
+ ## API Methods
92
+
93
+ | Method | Description |
94
+ |--------|-------------|
95
+ | `get_status()` | Check API connection |
96
+ | `get_settings()` | Get settings |
97
+ | `get_sync_status()` | Get sync status |
98
+ | `refresh_sync(profile_id?)` | Trigger sync |
99
+ | `get_profiles()` | List profiles |
100
+ | `create_profile(request)` | Create profile |
101
+ | `update_profile(id, **fields)` | Update profile |
102
+ | `delete_profile(id)` | Delete profile |
103
+ | `start_profile(id)` | Start profile |
104
+ | `stop_profile(id)` | Stop profile |
105
+ | `duplicate_profile(id, opts?)` | Duplicate profile |
106
+ | `get_automations()` | List automations |
107
+ | `run_automation(id, request)` | Run automation |
108
+ | `get_groups()` | List groups |
109
+ | `create_group(request)` | Create group |
110
+ | `update_group(id, **fields)` | Update group |
111
+ | `delete_group(id)` | Delete group |
112
+ | `get_proxies()` | List proxies |
113
+ | `create_proxy(request)` | Create proxy |
114
+ | `check_proxy(host, port, ...)` | Check proxy |
115
+ | `check_proxies_bulk(proxies)` | Bulk check |
116
+ | `delete_proxy(id)` | Delete proxy |
117
+ | `get_extensions()` | List extensions |
118
+ | `delete_extension(id)` | Delete extension |
119
+ | `get_profile_extensions(id)` | Get profile extensions |
120
+ | `set_profile_extensions(id, ids)` | Set profile extensions |
121
+
122
+ ## Error Handling
123
+
124
+ ```python
125
+ from antybrowser import AntybrowserError
126
+
127
+ try:
128
+ await client.get_profiles()
129
+ except AntybrowserError as e:
130
+ print(f"Status: {e.status_code}")
131
+ print(f"Body: {e.response_body}")
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ antybrowser/__init__.py
4
+ antybrowser/client.py
5
+ antybrowser/errors.py
6
+ antybrowser/py.typed
7
+ antybrowser/types.py
8
+ antybrowser.egg-info/PKG-INFO
9
+ antybrowser.egg-info/SOURCES.txt
10
+ antybrowser.egg-info/dependency_links.txt
11
+ antybrowser.egg-info/requires.txt
12
+ antybrowser.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ httpx>=0.25.0
2
+
3
+ [dev]
4
+ pytest
5
+ pytest-asyncio
@@ -0,0 +1 @@
1
+ antybrowser
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "antybrowser"
7
+ version = "1.0.2"
8
+ description = "Official Antybrowser SDK — Python client for the Antybrowser Local API"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Antybrowser Team" }]
13
+ keywords = ["antybrowser", "anti-detect", "browser-automation", "fingerprint", "multi-accounting"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = [
27
+ "httpx>=0.25.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://antybrowser.com"
32
+ Repository = "https://github.com/antybrowser/SDK"
33
+ Documentation = "https://github.com/antybrowser/SDK/tree/main/py"
34
+
35
+ [project.optional-dependencies]
36
+ dev = ["pytest", "pytest-asyncio"]
37
+
38
+ [tool.setuptools.packages.find]
39
+ include = ["antybrowser*"]
40
+
41
+ [tool.setuptools.package-data]
42
+ antybrowser = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+