mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-19 14:07:19 +02:00
feat: SDK for new routes
This commit is contained in:
1 parent
74c507bd9b
commit
9797b8b148
10 files changed
+1115
-12
No files matched your search
@@ -22,15 +22,18 @@ from .errors import DonutConnectionError, DonutError, error_for_status
|
||||
from .models import (
|
||||
AgentClick,
|
||||
AgentTyping,
|
||||
ApiBulkCreateResponse,
|
||||
ApiGroupResponse,
|
||||
ApiProfileResponse,
|
||||
ApiProfilesResponse,
|
||||
ApiProxyResponse,
|
||||
ApiRemoteSessionsResponse,
|
||||
ApiStatusResponse,
|
||||
ApiVpnExportResponse,
|
||||
ApiVpnResponse,
|
||||
BatchRunResponse,
|
||||
BatchStopResponse,
|
||||
BulkEditResponse,
|
||||
CookieBotConflictCheck,
|
||||
CookieBotPresetList,
|
||||
CookieBotRun,
|
||||
@@ -56,14 +59,20 @@ from .models import (
|
||||
PerceptionPage,
|
||||
PickedElement,
|
||||
ProfileImportBatchResult,
|
||||
ProfilePatch,
|
||||
ProfileTemplate,
|
||||
ProxyPair,
|
||||
ProxySettings,
|
||||
RemoteHoursQuota,
|
||||
RemoteSessionState,
|
||||
RotationResult,
|
||||
RunProfileResponse,
|
||||
RunRemoteResponse,
|
||||
SetCloudSyncResponse,
|
||||
StopRemoteResponse,
|
||||
StorageState,
|
||||
StorageStateImport,
|
||||
TemplateSettings,
|
||||
WayfernConfig,
|
||||
)
|
||||
|
||||
@@ -289,6 +298,8 @@ class DonutClient:
|
||||
ephemeral: Optional[bool] = None,
|
||||
temporary: Optional[bool] = None,
|
||||
dns_server: Optional[str] = None,
|
||||
status_id: Optional[str] = None,
|
||||
start_urls: Optional[Sequence[str]] = None,
|
||||
) -> ApiProfileResponse:
|
||||
"""POST /v1/profiles
|
||||
|
||||
@@ -298,7 +309,8 @@ class DonutClient:
|
||||
names through the profile's proxy or VPN: ``https://host/path``,
|
||||
``tls://host[:port]``, ``tcp://host[:port]`` or a bare ``host[:port]``;
|
||||
a bare address of a well-known resolver such as ``"1.1.1.1"`` uses its
|
||||
DNS over HTTPS endpoint.
|
||||
DNS over HTTPS endpoint. ``status_id`` comes from :meth:`list_statuses`;
|
||||
``start_urls`` is at most 20 http(s) URLs.
|
||||
"""
|
||||
return self._request(
|
||||
"POST",
|
||||
@@ -317,6 +329,8 @@ class DonutClient:
|
||||
ephemeral=ephemeral,
|
||||
temporary=temporary,
|
||||
dns_server=dns_server,
|
||||
status_id=status_id,
|
||||
start_urls=list(start_urls) if start_urls is not None else None,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -337,13 +351,16 @@ class DonutClient:
|
||||
sync_mode: Optional[str] = None,
|
||||
clear_on_close: Optional[bool] = None,
|
||||
dns_server: Optional[str] = None,
|
||||
status_id: Optional[str] = None,
|
||||
start_urls: Optional[Sequence[str]] = None,
|
||||
) -> ApiProfileResponse:
|
||||
"""PUT /v1/profiles/{id}
|
||||
|
||||
A profile's browser engine is fixed at creation, so there is no
|
||||
``browser`` argument. Pass ``proxy_id=""`` or ``vpn_id=""`` to detach
|
||||
one; leaving either unset changes nothing. ``dns_server=""`` lets the
|
||||
proxy resolve names again.
|
||||
proxy resolve names again, ``status_id=""`` clears the status, and
|
||||
``start_urls=[]`` removes the start pages.
|
||||
"""
|
||||
return self._request(
|
||||
"PUT",
|
||||
@@ -364,6 +381,8 @@ class DonutClient:
|
||||
sync_mode=sync_mode,
|
||||
clear_on_close=clear_on_close,
|
||||
dns_server=dns_server,
|
||||
status_id=status_id,
|
||||
start_urls=list(start_urls) if start_urls is not None else None,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -497,6 +516,51 @@ class DonutClient:
|
||||
body={"content": content},
|
||||
)
|
||||
|
||||
def export_storage_state(
|
||||
self, profile_id: str, *, include_cookies: Optional[bool] = None
|
||||
) -> StorageState:
|
||||
"""GET /v1/profiles/{id}/storage-state
|
||||
|
||||
Playwright's ``storageState``: each origin's Local Storage, plus the
|
||||
cookies when ``include_cookies=True``. Readable while the browser runs,
|
||||
but writes from the last few seconds may not be on disk yet.
|
||||
"""
|
||||
return self._request(
|
||||
"GET",
|
||||
f"/v1/profiles/{_segment(profile_id)}/storage-state",
|
||||
query=_query(include_cookies=include_cookies),
|
||||
)
|
||||
|
||||
def import_storage_state(self, profile_id: str, state: StorageState) -> StorageStateImport:
|
||||
"""POST /v1/profiles/{id}/storage-state
|
||||
|
||||
``state`` is a Playwright ``storageState``; what
|
||||
``context.storage_state()`` returns can be passed as is. An item
|
||||
replaces the one of the same name, and the origin's other items stay.
|
||||
The browser must be stopped.
|
||||
"""
|
||||
return self._request(
|
||||
"POST",
|
||||
f"/v1/profiles/{_segment(profile_id)}/storage-state",
|
||||
body=dict(state),
|
||||
)
|
||||
|
||||
def bulk_edit_profiles(
|
||||
self, profile_ids: Sequence[str], patch: ProfilePatch
|
||||
) -> BulkEditResponse:
|
||||
"""POST /v1/profiles/bulk-edit
|
||||
|
||||
Every profile is checked before any is saved, so a bad value, an
|
||||
unknown profile or a running browser changes nothing. ``patch`` is sent
|
||||
as given: a ``None`` in it clears that field rather than being dropped.
|
||||
``changed`` counts only the profiles that really changed.
|
||||
"""
|
||||
return self._request(
|
||||
"POST",
|
||||
"/v1/profiles/bulk-edit",
|
||||
body={"profile_ids": list(profile_ids), "patch": dict(patch)},
|
||||
)
|
||||
|
||||
def distribute_proxies(self, pairs: Sequence[ProxyPair]) -> DistributeProxiesResponse:
|
||||
"""POST /v1/profiles/distribute-proxies
|
||||
|
||||
@@ -849,6 +913,101 @@ class DonutClient:
|
||||
"""GET /v1/tags"""
|
||||
return self._request("GET", "/v1/tags")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Statuses
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def list_statuses(self) -> List[ApiStatusResponse]:
|
||||
"""GET /v1/statuses"""
|
||||
return self._request("GET", "/v1/statuses")
|
||||
|
||||
def create_status(self, *, name: str, color: str) -> ApiStatusResponse:
|
||||
"""POST /v1/statuses
|
||||
|
||||
``name`` is 1 to 40 characters, unique regardless of case, and
|
||||
``color`` is ``#rrggbb``.
|
||||
"""
|
||||
return self._request("POST", "/v1/statuses", body={"name": name, "color": color})
|
||||
|
||||
def update_status(self, status_id: str, *, name: str, color: str) -> ApiStatusResponse:
|
||||
"""PUT /v1/statuses/{id}"""
|
||||
return self._request(
|
||||
"PUT",
|
||||
f"/v1/statuses/{_segment(status_id)}",
|
||||
body={"name": name, "color": color},
|
||||
)
|
||||
|
||||
def delete_status(self, status_id: str) -> None:
|
||||
"""DELETE /v1/statuses/{id}
|
||||
|
||||
The status is also taken off every profile that had it.
|
||||
"""
|
||||
self._request("DELETE", f"/v1/statuses/{_segment(status_id)}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Templates
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def list_templates(self) -> List[ProfileTemplate]:
|
||||
"""GET /v1/templates"""
|
||||
return self._request("GET", "/v1/templates")
|
||||
|
||||
def create_template(
|
||||
self, *, name: str, settings: Optional[TemplateSettings] = None
|
||||
) -> ProfileTemplate:
|
||||
"""POST /v1/templates
|
||||
|
||||
``name`` is 1 to 60 characters, unique regardless of case. Device
|
||||
fields in ``settings["wayfern_config"]`` are dropped: every profile
|
||||
made from the template gets its own device.
|
||||
"""
|
||||
return self._request(
|
||||
"POST",
|
||||
"/v1/templates",
|
||||
body=_body(
|
||||
name=name, settings=dict(settings) if settings is not None else None
|
||||
),
|
||||
)
|
||||
|
||||
def update_template(
|
||||
self, template_id: str, *, name: str, settings: Optional[TemplateSettings] = None
|
||||
) -> ProfileTemplate:
|
||||
"""PUT /v1/templates/{id}
|
||||
|
||||
Replaces the whole template. Settings left out are reset to empty, so
|
||||
to rename one, send its current ``settings`` back with the new name.
|
||||
"""
|
||||
return self._request(
|
||||
"PUT",
|
||||
f"/v1/templates/{_segment(template_id)}",
|
||||
body=_body(
|
||||
name=name, settings=dict(settings) if settings is not None else None
|
||||
),
|
||||
)
|
||||
|
||||
def delete_template(self, template_id: str) -> None:
|
||||
"""DELETE /v1/templates/{id}
|
||||
|
||||
Profiles made from the template keep their settings.
|
||||
"""
|
||||
self._request("DELETE", f"/v1/templates/{_segment(template_id)}")
|
||||
|
||||
def create_profiles_from_template(
|
||||
self, template_id: str, *, count: int, name: Optional[str] = None
|
||||
) -> ApiBulkCreateResponse:
|
||||
"""POST /v1/templates/{id}/profiles
|
||||
|
||||
Makes ``count`` (1 to 100) profiles with the newest downloaded Wayfern,
|
||||
each with its own device. One profile takes ``name``; several take it
|
||||
followed by numbers. When ``error`` is set, creation stopped early and
|
||||
``profiles`` lists what was made.
|
||||
"""
|
||||
return self._request(
|
||||
"POST",
|
||||
f"/v1/templates/{_segment(template_id)}/profiles",
|
||||
body=_body(count=count, name=name),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Proxies
|
||||
# ------------------------------------------------------------------
|
||||
@@ -861,12 +1020,24 @@ class DonutClient:
|
||||
"""GET /v1/proxies/{id}"""
|
||||
return self._request("GET", f"/v1/proxies/{_segment(proxy_id)}")
|
||||
|
||||
def create_proxy(self, *, name: str, proxy_settings: ProxySettings) -> ApiProxyResponse:
|
||||
"""POST /v1/proxies"""
|
||||
def create_proxy(
|
||||
self,
|
||||
*,
|
||||
name: str,
|
||||
proxy_settings: ProxySettings,
|
||||
rotation_url: Optional[str] = None,
|
||||
) -> ApiProxyResponse:
|
||||
"""POST /v1/proxies
|
||||
|
||||
``rotation_url`` is the provider's http(s) "change IP" link, used by
|
||||
:meth:`rotate_proxy`.
|
||||
"""
|
||||
return self._request(
|
||||
"POST",
|
||||
"/v1/proxies",
|
||||
body={"name": name, "proxy_settings": dict(proxy_settings)},
|
||||
body=_body(
|
||||
name=name, proxy_settings=dict(proxy_settings), rotation_url=rotation_url
|
||||
),
|
||||
)
|
||||
|
||||
def update_proxy(
|
||||
@@ -875,14 +1046,19 @@ class DonutClient:
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
proxy_settings: Optional[ProxySettings] = None,
|
||||
rotation_url: Optional[str] = None,
|
||||
) -> ApiProxyResponse:
|
||||
"""PUT /v1/proxies/{id}"""
|
||||
"""PUT /v1/proxies/{id}
|
||||
|
||||
``rotation_url=""`` clears the "change IP" link.
|
||||
"""
|
||||
return self._request(
|
||||
"PUT",
|
||||
f"/v1/proxies/{_segment(proxy_id)}",
|
||||
body=_body(
|
||||
name=name,
|
||||
proxy_settings=dict(proxy_settings) if proxy_settings is not None else None,
|
||||
rotation_url=rotation_url,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -890,6 +1066,16 @@ class DonutClient:
|
||||
"""DELETE /v1/proxies/{id}"""
|
||||
self._request("DELETE", f"/v1/proxies/{_segment(proxy_id)}")
|
||||
|
||||
def rotate_proxy(self, proxy_id: str) -> RotationResult:
|
||||
"""POST /v1/proxies/{id}/rotate
|
||||
|
||||
Calls the proxy's "change IP" link; every profile behind the proxy
|
||||
moves to the new IP. A proxy without a link raises
|
||||
:class:`~donutbrowser.errors.ValidationError`, and a provider that
|
||||
refuses or does not answer raises :class:`~donutbrowser.errors.BadGateway`.
|
||||
"""
|
||||
return self._request("POST", f"/v1/proxies/{_segment(proxy_id)}/rotate")
|
||||
|
||||
def import_proxies(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -60,6 +60,7 @@ OPERATIONS: Dict[Operation, str] = {
|
||||
("POST", "/v1/profiles"): "create_profile",
|
||||
("POST", "/v1/profiles/batch/run"): "batch_run_profiles",
|
||||
("POST", "/v1/profiles/batch/stop"): "batch_stop_profiles",
|
||||
("POST", "/v1/profiles/bulk-edit"): "bulk_edit_profiles",
|
||||
("POST", "/v1/profiles/distribute-proxies"): "distribute_proxies",
|
||||
("POST", "/v1/profiles/import"): "import_profiles",
|
||||
("GET", "/v1/profiles/import/detect"): "detect_import_profiles",
|
||||
@@ -78,17 +79,29 @@ OPERATIONS: Dict[Operation, str] = {
|
||||
("POST", "/v1/profiles/{id}/open-url"): "open_url",
|
||||
("POST", "/v1/profiles/{id}/run"): "run_profile",
|
||||
("POST", "/v1/profiles/{id}/run-remote"): "run_profile_remote",
|
||||
("GET", "/v1/profiles/{id}/storage-state"): "export_storage_state",
|
||||
("POST", "/v1/profiles/{id}/storage-state"): "import_storage_state",
|
||||
("GET", "/v1/proxies"): "list_proxies",
|
||||
("POST", "/v1/proxies"): "create_proxy",
|
||||
("POST", "/v1/proxies/import"): "import_proxies",
|
||||
("DELETE", "/v1/proxies/{id}"): "delete_proxy",
|
||||
("GET", "/v1/proxies/{id}"): "get_proxy",
|
||||
("PUT", "/v1/proxies/{id}"): "update_proxy",
|
||||
("POST", "/v1/proxies/{id}/rotate"): "rotate_proxy",
|
||||
("GET", "/v1/remote-hours"): "get_remote_hours",
|
||||
("GET", "/v1/remote-sessions"): "list_remote_sessions",
|
||||
("DELETE", "/v1/remote-sessions/{id}"): "stop_remote_session",
|
||||
("GET", "/v1/remote-sessions/{id}"): "get_remote_session",
|
||||
("GET", "/v1/statuses"): "list_statuses",
|
||||
("POST", "/v1/statuses"): "create_status",
|
||||
("DELETE", "/v1/statuses/{id}"): "delete_status",
|
||||
("PUT", "/v1/statuses/{id}"): "update_status",
|
||||
("GET", "/v1/tags"): "list_tags",
|
||||
("GET", "/v1/templates"): "list_templates",
|
||||
("POST", "/v1/templates"): "create_template",
|
||||
("DELETE", "/v1/templates/{id}"): "delete_template",
|
||||
("PUT", "/v1/templates/{id}"): "update_template",
|
||||
("POST", "/v1/templates/{id}/profiles"): "create_profiles_from_template",
|
||||
("GET", "/v1/vpns"): "list_vpns",
|
||||
("POST", "/v1/vpns"): "create_vpn",
|
||||
("POST", "/v1/vpns/import"): "import_vpn",
|
||||
|
||||
@@ -17,14 +17,25 @@ passed straight into the next.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, TypedDict
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
|
||||
__all__ = [
|
||||
"ApiProfile",
|
||||
"ApiProfilesResponse",
|
||||
"ApiProfileResponse",
|
||||
"ApiGroupResponse",
|
||||
"ApiStatusResponse",
|
||||
"ProfilePatch",
|
||||
"BulkEditResponse",
|
||||
"TemplateSettings",
|
||||
"ProfileTemplate",
|
||||
"ApiBulkCreateResponse",
|
||||
"StorageItem",
|
||||
"OriginStorage",
|
||||
"StorageState",
|
||||
"StorageStateImport",
|
||||
"ApiProxyResponse",
|
||||
"RotationResult",
|
||||
"ApiVpnResponse",
|
||||
"ApiVpnExportResponse",
|
||||
"DownloadBrowserResponse",
|
||||
@@ -102,6 +113,7 @@ class _ApiProfileRequired(TypedDict):
|
||||
tags: List[str]
|
||||
is_running: bool
|
||||
proxy_bypass_rules: List[str]
|
||||
start_urls: List[str]
|
||||
ephemeral: bool
|
||||
temporary: bool
|
||||
clear_on_close: bool
|
||||
@@ -118,6 +130,7 @@ class ApiProfile(_ApiProfileRequired, total=False):
|
||||
group_id: str
|
||||
vpn_id: str
|
||||
dns_server: str
|
||||
status_id: str
|
||||
extension_group_id: str
|
||||
host_os: str
|
||||
fingerprint_os: str
|
||||
@@ -138,12 +151,146 @@ class ApiGroupResponse(TypedDict):
|
||||
profile_count: int
|
||||
|
||||
|
||||
class ApiProxyResponse(TypedDict):
|
||||
class ApiStatusResponse(TypedDict):
|
||||
"""A named, colored label for where a profile's account stands."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
color: str
|
||||
|
||||
|
||||
class ProfilePatch(TypedDict, total=False):
|
||||
"""One change applied to many profiles by ``bulk_edit_profiles``.
|
||||
|
||||
A key left out keeps that field as it is. For the fields typed
|
||||
``Optional``, an explicit ``None`` (JSON ``null``) clears the field, so
|
||||
the patch is sent exactly as given. An unknown key is refused with 400.
|
||||
"""
|
||||
|
||||
status_id: Optional[str]
|
||||
tags_add: List[str]
|
||||
tags_remove: List[str]
|
||||
note: Optional[str]
|
||||
group_id: Optional[str]
|
||||
extension_group_id: Optional[str]
|
||||
dns_blocklist: Optional[str]
|
||||
dns_server: Optional[str]
|
||||
proxy_bypass_rules: List[str]
|
||||
start_urls: List[str]
|
||||
launch_hook: Optional[str]
|
||||
clear_on_close: bool
|
||||
randomize_fingerprint_on_launch: bool
|
||||
webrtc_mode: Optional[str]
|
||||
block_images: bool
|
||||
block_webgl: bool
|
||||
restore_session: bool
|
||||
new_device: bool
|
||||
|
||||
|
||||
class BulkEditResponse(TypedDict):
|
||||
changed: int
|
||||
|
||||
|
||||
class TemplateSettings(TypedDict, total=False):
|
||||
"""What every profile made from a template starts with.
|
||||
|
||||
A key left out is empty. ``proxy_ids``, when not empty, wins over
|
||||
``proxy_id`` and spreads new profiles across those proxies in turn.
|
||||
"""
|
||||
|
||||
name_prefix: Optional[str]
|
||||
proxy_id: Optional[str]
|
||||
vpn_id: Optional[str]
|
||||
proxy_ids: List[str]
|
||||
group_id: Optional[str]
|
||||
tags: List[str]
|
||||
status_id: Optional[str]
|
||||
note: Optional[str]
|
||||
extension_group_id: Optional[str]
|
||||
dns_blocklist: Optional[str]
|
||||
dns_server: Optional[str]
|
||||
proxy_bypass_rules: List[str]
|
||||
start_urls: List[str]
|
||||
launch_hook: Optional[str]
|
||||
ephemeral: bool
|
||||
clear_on_close: bool
|
||||
wayfern_config: Optional[WayfernConfig]
|
||||
|
||||
|
||||
class ProfileTemplate(TypedDict):
|
||||
id: str
|
||||
name: str
|
||||
settings: TemplateSettings
|
||||
position: int
|
||||
updated_at: int
|
||||
|
||||
|
||||
class _ApiBulkCreateResponseRequired(TypedDict):
|
||||
profiles: List[ApiProfile]
|
||||
|
||||
|
||||
class ApiBulkCreateResponse(_ApiBulkCreateResponseRequired, total=False):
|
||||
"""``error`` is a ``{"code": ...}`` payload when creation stopped early."""
|
||||
|
||||
error: str
|
||||
|
||||
|
||||
class StorageItem(TypedDict):
|
||||
name: str
|
||||
value: str
|
||||
|
||||
|
||||
class _OriginStorageRequired(TypedDict):
|
||||
origin: str
|
||||
|
||||
|
||||
class OriginStorage(_OriginStorageRequired, total=False):
|
||||
# Playwright's own spelling.
|
||||
localStorage: List[StorageItem]
|
||||
|
||||
|
||||
class StorageState(TypedDict, total=False):
|
||||
"""Playwright's ``storageState``: cookies and each origin's Local Storage.
|
||||
|
||||
Each cookie is Playwright's own object (``name``, ``value``, ``domain``,
|
||||
``path``, ``expires``, ``httpOnly``, ``secure``, ``sameSite``), passed
|
||||
through untouched.
|
||||
"""
|
||||
|
||||
cookies: List[Dict[str, Any]]
|
||||
origins: List[OriginStorage]
|
||||
|
||||
|
||||
class StorageStateImport(TypedDict):
|
||||
origins: int
|
||||
items: int
|
||||
cookies_imported: int
|
||||
cookies_replaced: int
|
||||
cookie_errors: List[str]
|
||||
|
||||
|
||||
class _ApiProxyResponseRequired(TypedDict):
|
||||
id: str
|
||||
name: str
|
||||
proxy_settings: ProxySettings
|
||||
|
||||
|
||||
class ApiProxyResponse(_ApiProxyResponseRequired, total=False):
|
||||
rotation_url: str
|
||||
|
||||
|
||||
class _RotationResultRequired(TypedDict):
|
||||
proxy_id: str
|
||||
elapsed_ms: int
|
||||
|
||||
|
||||
class RotationResult(_RotationResultRequired, total=False):
|
||||
"""``status`` is the provider's HTTP status, when it answered."""
|
||||
|
||||
status: int
|
||||
error: str
|
||||
|
||||
|
||||
class _ApiVpnRequired(TypedDict):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
@@ -31,6 +31,15 @@ Case = Tuple[
|
||||
|
||||
LOCATOR = {"role": "button", "name": "Sign in"}
|
||||
|
||||
STORAGE_STATE = {
|
||||
"cookies": [{"name": "sid", "value": "1", "domain": ".example.com", "path": "/"}],
|
||||
"origins": [
|
||||
{"origin": "https://example.com", "localStorage": [{"name": "k", "value": "v"}]}
|
||||
],
|
||||
}
|
||||
|
||||
TEMPLATE_SETTINGS = {"name_prefix": "Shop", "tags": ["eu"], "proxy_ids": ["x1", "x2"]}
|
||||
|
||||
CASES: List[Case] = [
|
||||
# -- profiles ----------------------------------------------------------
|
||||
("list_profiles", (), {}, "GET", "/v1/profiles", None, {}, "/v1/profiles"),
|
||||
@@ -55,6 +64,26 @@ CASES: List[Case] = [
|
||||
{},
|
||||
"/v1/profiles",
|
||||
),
|
||||
(
|
||||
"create_profile",
|
||||
(),
|
||||
{
|
||||
"name": "Started",
|
||||
"browser": "wayfern",
|
||||
"status_id": "st1",
|
||||
"start_urls": ["https://example.com"],
|
||||
},
|
||||
"POST",
|
||||
"/v1/profiles",
|
||||
{
|
||||
"name": "Started",
|
||||
"browser": "wayfern",
|
||||
"status_id": "st1",
|
||||
"start_urls": ["https://example.com"],
|
||||
},
|
||||
{},
|
||||
"/v1/profiles",
|
||||
),
|
||||
(
|
||||
"update_profile",
|
||||
("p1",),
|
||||
@@ -65,6 +94,16 @@ CASES: List[Case] = [
|
||||
{},
|
||||
"/v1/profiles/{id}",
|
||||
),
|
||||
(
|
||||
"update_profile",
|
||||
("p1",),
|
||||
{"status_id": "", "start_urls": []},
|
||||
"PUT",
|
||||
"/v1/profiles/p1",
|
||||
{"status_id": "", "start_urls": []},
|
||||
{},
|
||||
"/v1/profiles/{id}",
|
||||
),
|
||||
("delete_profile", ("p1",), {}, "DELETE", "/v1/profiles/p1", None, {}, "/v1/profiles/{id}"),
|
||||
(
|
||||
"run_profile",
|
||||
@@ -194,6 +233,49 @@ CASES: List[Case] = [
|
||||
{},
|
||||
"/v1/profiles/{id}/cookies/import",
|
||||
),
|
||||
(
|
||||
"export_storage_state",
|
||||
("p1",),
|
||||
{"include_cookies": True},
|
||||
"GET",
|
||||
"/v1/profiles/p1/storage-state",
|
||||
None,
|
||||
{"include_cookies": "true"},
|
||||
"/v1/profiles/{id}/storage-state",
|
||||
),
|
||||
(
|
||||
"export_storage_state",
|
||||
("p1",),
|
||||
{},
|
||||
"GET",
|
||||
"/v1/profiles/p1/storage-state",
|
||||
None,
|
||||
{},
|
||||
"/v1/profiles/{id}/storage-state",
|
||||
),
|
||||
(
|
||||
"import_storage_state",
|
||||
("p1", STORAGE_STATE),
|
||||
{},
|
||||
"POST",
|
||||
"/v1/profiles/p1/storage-state",
|
||||
STORAGE_STATE,
|
||||
{},
|
||||
"/v1/profiles/{id}/storage-state",
|
||||
),
|
||||
(
|
||||
"bulk_edit_profiles",
|
||||
(["p1", "p2"], {"status_id": None, "tags_add": ["eu"], "new_device": True}),
|
||||
{},
|
||||
"POST",
|
||||
"/v1/profiles/bulk-edit",
|
||||
{
|
||||
"profile_ids": ["p1", "p2"],
|
||||
"patch": {"status_id": None, "tags_add": ["eu"], "new_device": True},
|
||||
},
|
||||
{},
|
||||
"/v1/profiles/bulk-edit",
|
||||
),
|
||||
# -- agent -------------------------------------------------------------
|
||||
(
|
||||
"agent_perceive",
|
||||
@@ -440,6 +522,72 @@ CASES: List[Case] = [
|
||||
),
|
||||
("delete_group", ("g1",), {}, "DELETE", "/v1/groups/g1", None, {}, "/v1/groups/{id}"),
|
||||
("list_tags", (), {}, "GET", "/v1/tags", None, {}, "/v1/tags"),
|
||||
# -- statuses ----------------------------------------------------------
|
||||
("list_statuses", (), {}, "GET", "/v1/statuses", None, {}, "/v1/statuses"),
|
||||
(
|
||||
"create_status",
|
||||
(),
|
||||
{"name": "Warming", "color": "#ffaa00"},
|
||||
"POST",
|
||||
"/v1/statuses",
|
||||
{"name": "Warming", "color": "#ffaa00"},
|
||||
{},
|
||||
"/v1/statuses",
|
||||
),
|
||||
(
|
||||
"update_status",
|
||||
("st1",),
|
||||
{"name": "Ready", "color": "#00aa55"},
|
||||
"PUT",
|
||||
"/v1/statuses/st1",
|
||||
{"name": "Ready", "color": "#00aa55"},
|
||||
{},
|
||||
"/v1/statuses/{id}",
|
||||
),
|
||||
("delete_status", ("st1",), {}, "DELETE", "/v1/statuses/st1", None, {}, "/v1/statuses/{id}"),
|
||||
# -- templates ---------------------------------------------------------
|
||||
("list_templates", (), {}, "GET", "/v1/templates", None, {}, "/v1/templates"),
|
||||
(
|
||||
"create_template",
|
||||
(),
|
||||
{"name": "Shop EU", "settings": TEMPLATE_SETTINGS},
|
||||
"POST",
|
||||
"/v1/templates",
|
||||
{"name": "Shop EU", "settings": TEMPLATE_SETTINGS},
|
||||
{},
|
||||
"/v1/templates",
|
||||
),
|
||||
(
|
||||
"create_template",
|
||||
(),
|
||||
{"name": "Empty"},
|
||||
"POST",
|
||||
"/v1/templates",
|
||||
{"name": "Empty"},
|
||||
{},
|
||||
"/v1/templates",
|
||||
),
|
||||
(
|
||||
"update_template",
|
||||
("t1",),
|
||||
{"name": "Shop EU 2", "settings": TEMPLATE_SETTINGS},
|
||||
"PUT",
|
||||
"/v1/templates/t1",
|
||||
{"name": "Shop EU 2", "settings": TEMPLATE_SETTINGS},
|
||||
{},
|
||||
"/v1/templates/{id}",
|
||||
),
|
||||
("delete_template", ("t1",), {}, "DELETE", "/v1/templates/t1", None, {}, "/v1/templates/{id}"),
|
||||
(
|
||||
"create_profiles_from_template",
|
||||
("t1",),
|
||||
{"count": 3, "name": "Shop"},
|
||||
"POST",
|
||||
"/v1/templates/t1/profiles",
|
||||
{"count": 3, "name": "Shop"},
|
||||
{},
|
||||
"/v1/templates/{id}/profiles",
|
||||
),
|
||||
# -- proxies -----------------------------------------------------------
|
||||
("list_proxies", (), {}, "GET", "/v1/proxies", None, {}, "/v1/proxies"),
|
||||
("get_proxy", ("x1",), {}, "GET", "/v1/proxies/x1", None, {}, "/v1/proxies/{id}"),
|
||||
@@ -453,6 +601,24 @@ CASES: List[Case] = [
|
||||
{},
|
||||
"/v1/proxies",
|
||||
),
|
||||
(
|
||||
"create_proxy",
|
||||
(),
|
||||
{
|
||||
"name": "Rotating",
|
||||
"proxy_settings": {"proxy_type": "http", "host": "h", "port": 8080},
|
||||
"rotation_url": "https://provider.example/rotate?key=1",
|
||||
},
|
||||
"POST",
|
||||
"/v1/proxies",
|
||||
{
|
||||
"name": "Rotating",
|
||||
"proxy_settings": {"proxy_type": "http", "host": "h", "port": 8080},
|
||||
"rotation_url": "https://provider.example/rotate?key=1",
|
||||
},
|
||||
{},
|
||||
"/v1/proxies",
|
||||
),
|
||||
(
|
||||
"update_proxy",
|
||||
("x1",),
|
||||
@@ -464,6 +630,26 @@ CASES: List[Case] = [
|
||||
"/v1/proxies/{id}",
|
||||
),
|
||||
("delete_proxy", ("x1",), {}, "DELETE", "/v1/proxies/x1", None, {}, "/v1/proxies/{id}"),
|
||||
(
|
||||
"update_proxy",
|
||||
("x1",),
|
||||
{"rotation_url": ""},
|
||||
"PUT",
|
||||
"/v1/proxies/x1",
|
||||
{"rotation_url": ""},
|
||||
{},
|
||||
"/v1/proxies/{id}",
|
||||
),
|
||||
(
|
||||
"rotate_proxy",
|
||||
("x1",),
|
||||
{},
|
||||
"POST",
|
||||
"/v1/proxies/x1/rotate",
|
||||
None,
|
||||
{},
|
||||
"/v1/proxies/{id}/rotate",
|
||||
),
|
||||
(
|
||||
"import_proxies",
|
||||
(),
|
||||
|
||||
Reference in new issue
Block a user