Skip to content

Commit e3587e2

Browse files
Captcha manual solve (#86)
* Captcha manual solve * bump version * Add manual captcha api
1 parent f900ac6 commit e3587e2

9 files changed

Lines changed: 517 additions & 13 deletions

File tree

hyperbrowser/client/managers/async_manager/session.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import warnings
33
from ....models.session import (
44
BasicResponse,
5+
CaptchaEvaluationParams,
6+
CaptchaEvaluationResponse,
57
CreateSessionParams,
68
GetSessionDownloadsUrlResponse,
79
GetSessionRecordingUrlResponse,
@@ -17,9 +19,13 @@
1719
UpdateSessionProfileParams,
1820
UpdateSessionProxyParams,
1921
UpdateSessionScreenParams,
22+
UpdateSessionSolveCaptchasParams,
23+
UpdateSessionSolveCaptchasResponse,
2024
SessionGetParams,
2125
)
2226

27+
CAPTCHA_EVALUATION_REQUEST_TIMEOUT_SECONDS = 185
28+
2329

2430
class SessionEventLogsManager:
2531
def __init__(self, client):
@@ -70,6 +76,21 @@ async def stop(self, id: str) -> BasicResponse:
7076
)
7177
return BasicResponse(**response.data)
7278

79+
async def evaluate_captcha(
80+
self,
81+
id: str,
82+
params: Optional[CaptchaEvaluationParams] = None,
83+
) -> CaptchaEvaluationResponse:
84+
params_obj = params or CaptchaEvaluationParams()
85+
response = await self._client.transport.post(
86+
self._client._build_url(f"/session/{id}/captcha/evaluate"),
87+
data=params_obj.model_dump(exclude_none=True, by_alias=True),
88+
timeout=max(
89+
self._client.timeout, CAPTCHA_EVALUATION_REQUEST_TIMEOUT_SECONDS
90+
),
91+
)
92+
return CaptchaEvaluationResponse(**response.data)
93+
7394
async def list(
7495
self, params: SessionListParams = SessionListParams()
7596
) -> SessionListResponse:
@@ -213,6 +234,36 @@ async def update_screen_size(
213234
)
214235
return BasicResponse(**response.data)
215236

237+
async def start_captcha_solving(
238+
self,
239+
id: str,
240+
params: Optional[UpdateSessionSolveCaptchasParams] = None,
241+
) -> UpdateSessionSolveCaptchasResponse:
242+
params_obj = params or UpdateSessionSolveCaptchasParams()
243+
response = await self._client.transport.put(
244+
self._client._build_url(f"/session/{id}/update"),
245+
data={
246+
"type": "solveCaptchas",
247+
"params": {
248+
"enabled": True,
249+
**params_obj.model_dump(exclude_none=True, by_alias=True),
250+
},
251+
},
252+
)
253+
return UpdateSessionSolveCaptchasResponse(**response.data)
254+
255+
async def stop_captcha_solving(self, id: str) -> UpdateSessionSolveCaptchasResponse:
256+
response = await self._client.transport.put(
257+
self._client._build_url(f"/session/{id}/update"),
258+
data={
259+
"type": "solveCaptchas",
260+
"params": {
261+
"enabled": False,
262+
},
263+
},
264+
)
265+
return UpdateSessionSolveCaptchasResponse(**response.data)
266+
216267
def _warn_update_profile_params_boolean_deprecated(self) -> None:
217268
if SessionManager._has_warned_update_profile_params_boolean_deprecated:
218269
return

hyperbrowser/client/managers/sync_manager/session.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import warnings
33
from ....models.session import (
44
BasicResponse,
5+
CaptchaEvaluationParams,
6+
CaptchaEvaluationResponse,
57
CreateSessionParams,
68
GetSessionDownloadsUrlResponse,
79
GetSessionRecordingUrlResponse,
@@ -16,9 +18,13 @@
1618
UpdateSessionProfileParams,
1719
UpdateSessionProxyParams,
1820
UpdateSessionScreenParams,
21+
UpdateSessionSolveCaptchasParams,
22+
UpdateSessionSolveCaptchasResponse,
1923
SessionGetParams,
2024
)
2125

26+
CAPTCHA_EVALUATION_REQUEST_TIMEOUT_SECONDS = 185
27+
2228

2329
class SessionEventLogsManager:
2430
def __init__(self, client):
@@ -69,6 +75,21 @@ def stop(self, id: str) -> BasicResponse:
6975
)
7076
return BasicResponse(**response.data)
7177

78+
def evaluate_captcha(
79+
self,
80+
id: str,
81+
params: Optional[CaptchaEvaluationParams] = None,
82+
) -> CaptchaEvaluationResponse:
83+
params_obj = params or CaptchaEvaluationParams()
84+
response = self._client.transport.post(
85+
self._client._build_url(f"/session/{id}/captcha/evaluate"),
86+
data=params_obj.model_dump(exclude_none=True, by_alias=True),
87+
timeout=max(
88+
self._client.timeout, CAPTCHA_EVALUATION_REQUEST_TIMEOUT_SECONDS
89+
),
90+
)
91+
return CaptchaEvaluationResponse(**response.data)
92+
7293
def list(
7394
self, params: SessionListParams = SessionListParams()
7495
) -> SessionListResponse:
@@ -208,6 +229,36 @@ def update_screen_size(
208229
)
209230
return BasicResponse(**response.data)
210231

232+
def start_captcha_solving(
233+
self,
234+
id: str,
235+
params: Optional[UpdateSessionSolveCaptchasParams] = None,
236+
) -> UpdateSessionSolveCaptchasResponse:
237+
params_obj = params or UpdateSessionSolveCaptchasParams()
238+
response = self._client.transport.put(
239+
self._client._build_url(f"/session/{id}/update"),
240+
data={
241+
"type": "solveCaptchas",
242+
"params": {
243+
"enabled": True,
244+
**params_obj.model_dump(exclude_none=True, by_alias=True),
245+
},
246+
},
247+
)
248+
return UpdateSessionSolveCaptchasResponse(**response.data)
249+
250+
def stop_captcha_solving(self, id: str) -> UpdateSessionSolveCaptchasResponse:
251+
response = self._client.transport.put(
252+
self._client._build_url(f"/session/{id}/update"),
253+
data={
254+
"type": "solveCaptchas",
255+
"params": {
256+
"enabled": False,
257+
},
258+
},
259+
)
260+
return UpdateSessionSolveCaptchasResponse(**response.data)
261+
211262
def _warn_update_profile_params_boolean_deprecated(self) -> None:
212263
if SessionManager._has_warned_update_profile_params_boolean_deprecated:
213264
return

hyperbrowser/models/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,18 @@
262262
SessionLaunchState,
263263
UploadFileResponse,
264264
ImageCaptchaParam,
265+
CaptchaSolverType,
266+
CaptchaEvaluationPageResult,
267+
CaptchaEvaluationParams,
268+
CaptchaEvaluationResponse,
269+
CaptchaEvaluationTarget,
270+
CaptchaEvaluationType,
265271
UpdateSessionProfileParams,
266272
UpdateSessionProxyLocationParams,
267273
UpdateSessionProxyParams,
268274
UpdateSessionScreenParams,
275+
UpdateSessionSolveCaptchasParams,
276+
UpdateSessionSolveCaptchasResponse,
269277
)
270278
from .sandbox import (
271279
SandboxStatus,
@@ -524,10 +532,18 @@
524532
"SessionLaunchState",
525533
"UploadFileResponse",
526534
"ImageCaptchaParam",
535+
"CaptchaSolverType",
536+
"CaptchaEvaluationPageResult",
537+
"CaptchaEvaluationParams",
538+
"CaptchaEvaluationResponse",
539+
"CaptchaEvaluationTarget",
540+
"CaptchaEvaluationType",
527541
"UpdateSessionProfileParams",
528542
"UpdateSessionProxyLocationParams",
529543
"UpdateSessionProxyParams",
530544
"UpdateSessionScreenParams",
545+
"UpdateSessionSolveCaptchasParams",
546+
"UpdateSessionSolveCaptchasResponse",
531547
# sandbox
532548
"SandboxStatus",
533549
"SandboxRegion",
@@ -551,6 +567,7 @@
551567
"SandboxMemorySnapshotResult",
552568
"SandboxExposeParams",
553569
"SandboxExposeResult",
570+
"SandboxUnexposeResult",
554571
"SandboxProcessStatus",
555572
"SandboxExecParams",
556573
"SandboxProcessSummary",

hyperbrowser/models/session.py

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Any, List, Literal, Optional, Union, Dict
2+
from typing import Any, Dict, List, Literal, Optional, Union
33

44
from pydantic import BaseModel, ConfigDict, Field, field_validator
55

@@ -16,6 +16,16 @@
1616
)
1717

1818
SessionStatus = Literal["active", "closed", "error"]
19+
CaptchaSolverType = Literal["visual"]
20+
CaptchaEvaluationType = Literal[
21+
"turnstile",
22+
"cloudflare-challenge",
23+
"aliexpress",
24+
"recaptcha",
25+
"recaptcha-visual",
26+
"amazon",
27+
]
28+
CaptchaEvaluationTarget = CaptchaEvaluationType
1929

2030

2131
class BasicResponse(BaseModel):
@@ -134,6 +144,7 @@ class SessionLaunchState(BaseModel):
134144
)
135145
enable_log_capture: Optional[bool] = Field(default=None, alias="enableLogCapture")
136146
accept_cookies: Optional[bool] = Field(default=None, alias="acceptCookies")
147+
solver_type: Optional[CaptchaSolverType] = Field(default=None, alias="solverType")
137148
profile: Optional[SessionProfile] = Field(default=None, alias="profile")
138149
static_ip_id: Optional[str] = Field(default=None, alias="staticIpId")
139150
save_downloads: Optional[bool] = Field(default=None, alias="saveDownloads")
@@ -303,6 +314,75 @@ class ImageCaptchaParam(BaseModel):
303314
input_selector: str = Field(serialization_alias="inputSelector")
304315

305316

317+
class CaptchaEvaluationParams(BaseModel):
318+
"""
319+
Parameters for manually evaluating captchas in a running session.
320+
"""
321+
322+
model_config = ConfigDict(
323+
populate_by_alias=True,
324+
)
325+
326+
captcha: Optional[CaptchaEvaluationTarget] = Field(default=None)
327+
captcha_type: Optional[CaptchaEvaluationTarget] = Field(
328+
default=None, serialization_alias="captchaType"
329+
)
330+
text: Optional[CaptchaEvaluationTarget] = Field(default=None)
331+
iterations: Optional[int] = Field(default=None)
332+
max_iterations: Optional[int] = Field(
333+
default=None, serialization_alias="maxIterations"
334+
)
335+
solver_type: Optional[CaptchaSolverType] = Field(
336+
default=None, serialization_alias="solverType"
337+
)
338+
image_captcha_params: Optional[List[ImageCaptchaParam]] = Field(
339+
default=None, serialization_alias="imageCaptchaParams"
340+
)
341+
use_gemini_captcha_solver: Optional[bool] = Field(
342+
default=None, serialization_alias="useGeminiCaptchaSolver"
343+
)
344+
use_ultra_stealth: Optional[bool] = Field(
345+
default=None, serialization_alias="useUltraStealth"
346+
)
347+
348+
349+
class CaptchaEvaluationPageResult(BaseModel):
350+
"""
351+
Result of manually evaluating captchas on a single page target.
352+
"""
353+
354+
model_config = ConfigDict(
355+
populate_by_alias=True,
356+
)
357+
358+
url: str = Field(alias="url")
359+
target_id: Optional[str] = Field(default=None, alias="targetId")
360+
iterations_run: int = Field(alias="iterationsRun")
361+
solved: bool = Field(alias="solved")
362+
solved_captchas: List[CaptchaEvaluationType] = Field(alias="solvedCaptchas")
363+
checked_captchas: List[CaptchaEvaluationType] = Field(alias="checkedCaptchas")
364+
captcha_solved_counts: Dict[str, int] = Field(alias="captchaSolvedCounts")
365+
last_solve_time: Dict[str, float] = Field(alias="lastSolveTime")
366+
367+
368+
class CaptchaEvaluationResponse(BaseModel):
369+
"""
370+
Response from manually evaluating captchas in a running session.
371+
"""
372+
373+
model_config = ConfigDict(
374+
populate_by_alias=True,
375+
)
376+
377+
success: bool = Field(alias="success")
378+
captcha: Optional[CaptchaEvaluationType] = Field(default=None, alias="captcha")
379+
iterations_requested: int = Field(alias="iterationsRequested")
380+
iterations_run: int = Field(alias="iterationsRun")
381+
solved: bool = Field(alias="solved")
382+
solved_captchas: List[CaptchaEvaluationType] = Field(alias="solvedCaptchas")
383+
pages: List[CaptchaEvaluationPageResult] = Field(alias="pages")
384+
385+
306386
class CreateSessionParams(BaseModel):
307387
"""
308388
Parameters for creating a new browser session.
@@ -337,6 +417,9 @@ class CreateSessionParams(BaseModel):
337417
locales: List[ISO639_1] = Field(default=["en"])
338418
screen: Optional[ScreenConfig] = Field(default=None)
339419
solve_captchas: bool = Field(default=False, serialization_alias="solveCaptchas")
420+
solver_type: Optional[CaptchaSolverType] = Field(
421+
default=None, serialization_alias="solverType"
422+
)
340423
adblock: bool = Field(default=False, serialization_alias="adblock")
341424
trackers: bool = Field(default=False, serialization_alias="trackers")
342425
annoyances: bool = Field(default=False, serialization_alias="annoyances")
@@ -480,6 +563,35 @@ class UploadFileResponse(BaseModel):
480563
original_name: Optional[str] = Field(default=None, alias="originalName")
481564

482565

566+
class UpdateSessionSolveCaptchasParams(BaseModel):
567+
"""
568+
Parameters for starting automatic captcha solving in a running session.
569+
"""
570+
571+
model_config = ConfigDict(
572+
populate_by_alias=True,
573+
)
574+
575+
solver_type: Optional[CaptchaSolverType] = Field(
576+
default=None,
577+
serialization_alias="solverType",
578+
)
579+
580+
581+
class UpdateSessionSolveCaptchasResponse(BasicResponse):
582+
"""
583+
Response from updating automatic captcha solving in a running session.
584+
"""
585+
586+
model_config = ConfigDict(
587+
populate_by_alias=True,
588+
)
589+
590+
solve_captchas: Optional[bool] = Field(default=None, alias="solveCaptchas")
591+
session_id: Optional[str] = Field(default=None, alias="sessionId")
592+
telemetry_ready: Optional[bool] = Field(default=None, alias="telemetryReady")
593+
594+
483595
class SessionEventLog(BaseModel):
484596
model_config = ConfigDict(
485597
populate_by_alias=True,

hyperbrowser/transport/async_transport.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async def _handle_response(self, response: httpx.Response) -> APIResponse:
5555
try:
5656
error_data = response.json()
5757
message = error_data.get("message") or error_data.get("error") or str(e)
58-
except:
58+
except Exception:
5959
message = str(e)
6060
raise HyperbrowserError(
6161
message,
@@ -67,13 +67,20 @@ async def _handle_response(self, response: httpx.Response) -> APIResponse:
6767
raise HyperbrowserError("Request failed", original_error=e)
6868

6969
async def post(
70-
self, url: str, data: Optional[dict] = None, files: Optional[dict] = None
70+
self,
71+
url: str,
72+
data: Optional[dict] = None,
73+
files: Optional[dict] = None,
74+
timeout: Optional[float] = None,
7175
) -> APIResponse:
7276
try:
77+
kwargs = {}
78+
if timeout is not None:
79+
kwargs["timeout"] = timeout
7380
if files:
74-
response = await self.client.post(url, data=data, files=files)
81+
response = await self.client.post(url, data=data, files=files, **kwargs)
7582
else:
76-
response = await self.client.post(url, json=data)
83+
response = await self.client.post(url, json=data, **kwargs)
7784
return await self._handle_response(response)
7885
except HyperbrowserError:
7986
raise

0 commit comments

Comments
 (0)