from typing import Optional, Dict, Any, Literal from ...types import ( ScrapeOptions, Document, BrowserExecuteResponse, BrowserDeleteResponse, ) from ...utils.normalize import normalize_document_input from ...utils.error_handler import handle_response_error from ...utils.validation import prepare_scrape_options, validate_scrape_options from ...utils.http_client_async import AsyncHttpClient async def _prepare_scrape_request(url: str, options: Optional[ScrapeOptions] = None) -> Dict[str, Any]: if not url or not url.strip(): raise ValueError("URL cannot be empty") payload: Dict[str, Any] = {"url": url.strip()} if options is not None: validated = validate_scrape_options(options) if validated is not None: opts = prepare_scrape_options(validated) if opts: payload.update(opts) return payload async def scrape(client: AsyncHttpClient, url: str, options: Optional[ScrapeOptions] = None) -> Document: payload = await _prepare_scrape_request(url, options) response = await client.post("/v2/scrape", payload) if response.status_code >= 400: handle_response_error(response, "scrape") body = response.json() if not body.get("success"): raise Exception(body.get("error", "Unknown error occurred")) document_data = body.get("data", {}) normalized = normalize_document_input(document_data) return Document(**normalized) async def interact( client: AsyncHttpClient, job_id: str, code: Optional[str] = None, *, prompt: Optional[str] = None, language: Literal["python", "node", "bash"] = "node", timeout: Optional[int] = None, origin: Optional[str] = None, ) -> BrowserExecuteResponse: if not job_id or not job_id.strip(): raise ValueError("Job ID cannot be empty") has_code = code and code.strip() has_prompt = prompt and prompt.strip() if not has_code and not has_prompt: raise ValueError("Either 'code' or 'prompt' must be provided") payload: Dict[str, Any] = { "language": language, } if has_code: payload["code"] = code if has_prompt: payload["prompt"] = prompt if timeout is not None: payload["timeout"] = timeout if origin is not None: payload["origin"] = origin response = await client.post(f"/v2/scrape/{job_id}/interact", payload) if response.status_code >= 400: handle_response_error(response, "interact with scrape browser") body = response.json() if not body.get("success"): raise Exception(body.get("error", "Unknown error occurred")) normalized = dict(body) if "exitCode" in normalized and "exit_code" not in normalized: normalized["exit_code"] = normalized["exitCode"] if "liveViewUrl" in normalized and "live_view_url" not in normalized: normalized["live_view_url"] = normalized["liveViewUrl"] if "interactiveLiveViewUrl" in normalized and "interactive_live_view_url" not in normalized: normalized["interactive_live_view_url"] = normalized["interactiveLiveViewUrl"] return BrowserExecuteResponse(**normalized) async def stop_interaction( client: AsyncHttpClient, job_id: str, ) -> BrowserDeleteResponse: if not job_id or not job_id.strip(): raise ValueError("Job ID cannot be empty") response = await client.delete(f"/v2/scrape/{job_id}/interact") if response.status_code >= 400: handle_response_error(response, "stop interaction") body = response.json() normalized = dict(body) if "sessionDurationMs" in normalized and "session_duration_ms" not in normalized: normalized["session_duration_ms"] = normalized["sessionDurationMs"] if "creditsBilled" in normalized and "credits_billed" not in normalized: normalized["credits_billed"] = normalized["creditsBilled"] return BrowserDeleteResponse(**normalized) async def stop_interactive_browser( client: AsyncHttpClient, job_id: str, ) -> BrowserDeleteResponse: """Deprecated alias for stop_interaction().""" return await stop_interaction(client, job_id) async def scrape_execute( client: AsyncHttpClient, job_id: str, code: Optional[str] = None, *, prompt: Optional[str] = None, language: Literal["python", "node", "bash"] = "node", timeout: Optional[int] = None, origin: Optional[str] = None, ) -> BrowserExecuteResponse: """Deprecated alias for interact().""" return await interact( client, job_id, code, prompt=prompt, language=language, timeout=timeout, origin=origin, ) async def delete_scrape_browser( client: AsyncHttpClient, job_id: str, ) -> BrowserDeleteResponse: """Deprecated alias for stop_interaction().""" return await stop_interaction(client, job_id)