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.
- antybrowser-1.0.2/PKG-INFO +136 -0
- antybrowser-1.0.2/README.md +109 -0
- antybrowser-1.0.2/antybrowser/__init__.py +44 -0
- antybrowser-1.0.2/antybrowser/client.py +268 -0
- antybrowser-1.0.2/antybrowser/errors.py +17 -0
- antybrowser-1.0.2/antybrowser/py.typed +1 -0
- antybrowser-1.0.2/antybrowser/types.py +347 -0
- antybrowser-1.0.2/antybrowser.egg-info/PKG-INFO +136 -0
- antybrowser-1.0.2/antybrowser.egg-info/SOURCES.txt +12 -0
- antybrowser-1.0.2/antybrowser.egg-info/dependency_links.txt +1 -0
- antybrowser-1.0.2/antybrowser.egg-info/requires.txt +5 -0
- antybrowser-1.0.2/antybrowser.egg-info/top_level.txt +1 -0
- antybrowser-1.0.2/pyproject.toml +42 -0
- antybrowser-1.0.2/setup.cfg +4 -0
|
@@ -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
|
+
[](https://pypi.org/project/antybrowser/)
|
|
33
|
+
[](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
|
+
[](https://pypi.org/project/antybrowser/)
|
|
6
|
+
[](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
|
+
[](https://pypi.org/project/antybrowser/)
|
|
33
|
+
[](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 @@
|
|
|
1
|
+
|
|
@@ -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"]
|