Skip to content

Commit

Permalink
Stability improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Xewdy444 committed Nov 29, 2023
1 parent c40b58b commit 9f3590b
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 78 deletions.
102 changes: 64 additions & 38 deletions playwright_recaptcha/recaptchav2/async_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,18 +177,6 @@ async def _random_delay(self, short: bool = True) -> None:
delay_time = random.randint(150, 350) if short else random.randint(1250, 1500)
await self._page.wait_for_timeout(delay_time)

async def _wait_for_value(self, attribute: str) -> None:
"""
Wait for an attribute to have a value.
Parameters
----------
attribute : str
The attribute.
"""
while getattr(self, attribute) is None:
await self._page.wait_for_timeout(250)

async def _get_capsolver_response(
self, recaptcha_box: AsyncRecaptchaBox, image_data: bytes
) -> Optional[Dict[str, Any]]:
Expand Down Expand Up @@ -359,7 +347,7 @@ async def _click_checkbox(self, recaptcha_box: AsyncRecaptchaBox) -> None:
raise RecaptchaRateLimitError

if await recaptcha_box.challenge_is_visible():
break
return

await self._page.wait_for_timeout(250)

Expand Down Expand Up @@ -387,12 +375,10 @@ async def _get_audio_url(self, recaptcha_box: AsyncRecaptchaBox) -> str:
raise RecaptchaRateLimitError

if await recaptcha_box.audio_challenge_is_visible():
break
return await recaptcha_box.audio_download_button.get_attribute("href")

await self._page.wait_for_timeout(250)

return await recaptcha_box.audio_download_button.get_attribute("href")

async def _submit_audio_text(
self, recaptcha_box: AsyncRecaptchaBox, text: str
) -> None:
Expand All @@ -415,10 +401,10 @@ async def _submit_audio_text(

async with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/userverify")
):
) as response:
await recaptcha_box.verify_button.click()

await self._wait_for_value("_token")
await response.value

while recaptcha_box.frames_are_attached():
if await recaptcha_box.rate_limit_is_visible():
Expand All @@ -429,7 +415,47 @@ async def _submit_audio_text(
or await recaptcha_box.solve_failure_is_visible()
or await recaptcha_box.challenge_is_solved()
):
break
return

await self._page.wait_for_timeout(250)

async def _submit_tile_answers(self, recaptcha_box: AsyncRecaptchaBox) -> None:
"""
Submit the reCAPTCHA image challenge tile answers.
Parameters
----------
recaptcha_box : AsyncRecaptchaBox
The reCAPTCHA box.
Raises
------
RecaptchaRateLimitError
If the reCAPTCHA rate limit has been exceeded.
"""
await recaptcha_box.verify_button.click()

while recaptcha_box.frames_are_attached():
if await recaptcha_box.rate_limit_is_visible():
raise RecaptchaRateLimitError

if (
await recaptcha_box.challenge_is_solved()
or await recaptcha_box.try_again_is_visible()
):
return

if (
await recaptcha_box.check_new_images_is_visible()
or await recaptcha_box.select_all_matching_is_visible()
):
async with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/payload")
) as response:
await recaptcha_box.new_challenge_button.click()

await response.value
return

await self._page.wait_for_timeout(250)

Expand All @@ -450,7 +476,6 @@ async def _solve_image_challenge(self, recaptcha_box: AsyncRecaptchaBox) -> None
If the reCAPTCHA rate limit has been exceeded.
"""
while recaptcha_box.frames_are_attached():
await self._wait_for_value("_payload_response")
await self._random_delay()

capsolver_response = await self._get_capsolver_response(
Expand All @@ -462,7 +487,13 @@ async def _solve_image_challenge(self, recaptcha_box: AsyncRecaptchaBox) -> None
or not capsolver_response["solution"]["objects"]
):
self._payload_response = None
await recaptcha_box.new_challenge_button.click()

async with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/payload")
) as response:
await recaptcha_box.new_challenge_button.click()

await response.value
continue

await self._solve_tiles(
Expand All @@ -475,25 +506,15 @@ async def _solve_image_challenge(self, recaptcha_box: AsyncRecaptchaBox) -> None
button = recaptcha_box.skip_button.or_(recaptcha_box.next_button)

if await button.is_visible():
await button.click()
continue

async with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/(payload|userverify)")
):
await recaptcha_box.verify_button.click()

if (
await recaptcha_box.check_new_images_is_visible()
or await recaptcha_box.select_all_matching_is_visible()
):
async with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/payload")
) as response:
await recaptcha_box.new_challenge_button.click()
else:
await self._wait_for_value("_token")

if await recaptcha_box.rate_limit_is_visible():
raise RecaptchaRateLimitError
await response.value
continue

await self._submit_tile_answers(recaptcha_box)
return

async def _solve_audio_challenge(self, recaptcha_box: AsyncRecaptchaBox) -> None:
Expand Down Expand Up @@ -521,11 +542,16 @@ async def _solve_audio_challenge(self, recaptcha_box: AsyncRecaptchaBox) -> None

async with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/payload")
):
) as response:
await recaptcha_box.new_challenge_button.click()

await response.value

await self._submit_audio_text(recaptcha_box, text)

while self._token is None:
await self._page.wait_for_timeout(250)

def close(self) -> None:
"""Remove the response listener."""
try:
Expand Down
6 changes: 2 additions & 4 deletions playwright_recaptcha/recaptchav2/recaptcha_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,7 @@ def from_frames(cls, frames: Iterable[SyncFrame]) -> SyncRecaptchaBox:
recaptcha_box = cls(anchor_frame, bframe_frame)

if (
recaptcha_box.checkbox.is_visible()
and not recaptcha_box.checkbox.is_checked()
not recaptcha_box.challenge_is_solved()
or recaptcha_box.audio_challenge_button.is_visible()
and recaptcha_box.audio_challenge_button.is_enabled()
or recaptcha_box.image_challenge_button.is_visible()
Expand Down Expand Up @@ -518,8 +517,7 @@ async def from_frames(cls, frames: Iterable[AsyncFrame]) -> AsyncRecaptchaBox:
recaptcha_box = cls(anchor_frame, bframe_frame)

if (
await recaptcha_box.checkbox.is_visible()
and not await recaptcha_box.checkbox.is_checked()
not await recaptcha_box.challenge_is_solved()
or await recaptcha_box.audio_challenge_button.is_visible()
and await recaptcha_box.audio_challenge_button.is_enabled()
or await recaptcha_box.image_challenge_button.is_visible()
Expand Down
91 changes: 55 additions & 36 deletions playwright_recaptcha/recaptchav2/sync_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,18 +139,6 @@ def _random_delay(self, short: bool = True) -> None:
delay_time = random.randint(150, 350) if short else random.randint(1250, 1500)
self._page.wait_for_timeout(delay_time)

def _wait_for_value(self, attribute: str) -> None:
"""
Wait for an attribute to have a value.
Parameters
----------
attribute : str
The attribute.
"""
while getattr(self, attribute) is None:
self._page.wait_for_timeout(250)

def _get_capsolver_response(
self, recaptcha_box: SyncRecaptchaBox, image_data: bytes
) -> Optional[Dict[str, Any]]:
Expand Down Expand Up @@ -307,7 +295,7 @@ def _click_checkbox(self, recaptcha_box: SyncRecaptchaBox) -> None:
raise RecaptchaRateLimitError

if recaptcha_box.challenge_is_visible():
break
return

self._page.wait_for_timeout(250)

Expand Down Expand Up @@ -335,12 +323,10 @@ def _get_audio_url(self, recaptcha_box: SyncRecaptchaBox) -> str:
raise RecaptchaRateLimitError

if recaptcha_box.audio_challenge_is_visible():
break
return recaptcha_box.audio_download_button.get_attribute("href")

self._page.wait_for_timeout(250)

return recaptcha_box.audio_download_button.get_attribute("href")

def _submit_audio_text(self, recaptcha_box: SyncRecaptchaBox, text: str) -> None:
"""
Submit the reCAPTCHA audio text.
Expand All @@ -364,8 +350,6 @@ def _submit_audio_text(self, recaptcha_box: SyncRecaptchaBox, text: str) -> None
):
recaptcha_box.verify_button.click()

self._wait_for_value("_token")

while recaptcha_box.frames_are_attached():
if recaptcha_box.rate_limit_is_visible():
raise RecaptchaRateLimitError
Expand All @@ -375,7 +359,46 @@ def _submit_audio_text(self, recaptcha_box: SyncRecaptchaBox, text: str) -> None
or recaptcha_box.solve_failure_is_visible()
or recaptcha_box.challenge_is_solved()
):
break
return

self._page.wait_for_timeout(250)

def _submit_tile_answers(self, recaptcha_box: SyncRecaptchaBox) -> None:
"""
Submit the reCAPTCHA image challenge tile answers.
Parameters
----------
recaptcha_box : SyncRecaptchaBox
The reCAPTCHA box.
Raises
------
RecaptchaRateLimitError
If the reCAPTCHA rate limit has been exceeded.
"""
recaptcha_box.verify_button.click()

while recaptcha_box.frames_are_attached():
if recaptcha_box.rate_limit_is_visible():
raise RecaptchaRateLimitError

if (
recaptcha_box.challenge_is_solved()
or recaptcha_box.try_again_is_visible()
):
return

if (
recaptcha_box.check_new_images_is_visible()
or recaptcha_box.select_all_matching_is_visible()
):
with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/payload")
):
recaptcha_box.new_challenge_button.click()

return

self._page.wait_for_timeout(250)

Expand All @@ -396,7 +419,6 @@ def _solve_image_challenge(self, recaptcha_box: SyncRecaptchaBox) -> None:
If the reCAPTCHA rate limit has been exceeded.
"""
while recaptcha_box.frames_are_attached():
self._wait_for_value("_payload_response")
self._random_delay()

capsolver_response = self._get_capsolver_response(
Expand All @@ -408,7 +430,12 @@ def _solve_image_challenge(self, recaptcha_box: SyncRecaptchaBox) -> None:
or not capsolver_response["solution"]["objects"]
):
self._payload_response = None
recaptcha_box.new_challenge_button.click()

with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/payload")
):
recaptcha_box.new_challenge_button.click()

continue

self._solve_tiles(recaptcha_box, capsolver_response["solution"]["objects"])
Expand All @@ -418,25 +445,14 @@ def _solve_image_challenge(self, recaptcha_box: SyncRecaptchaBox) -> None:
button = recaptcha_box.skip_button.or_(recaptcha_box.next_button)

if button.is_visible():
button.click()
continue

with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/(payload|userverify)")
):
recaptcha_box.verify_button.click()

if (
recaptcha_box.check_new_images_is_visible()
or recaptcha_box.select_all_matching_is_visible()
with self._page.expect_response(
re.compile("/recaptcha/(api2|enterprise)/payload")
):
recaptcha_box.new_challenge_button.click()
else:
self._wait_for_value("_token")

if recaptcha_box.rate_limit_is_visible():
raise RecaptchaRateLimitError
continue

self._submit_tile_answers(recaptcha_box)
return

def _solve_audio_challenge(self, recaptcha_box: SyncRecaptchaBox) -> None:
Expand Down Expand Up @@ -469,6 +485,9 @@ def _solve_audio_challenge(self, recaptcha_box: SyncRecaptchaBox) -> None:

self._submit_audio_text(recaptcha_box, text)

while self._token is None:
self._page.wait_for_timeout(250)

def close(self) -> None:
"""Remove the response listener."""
try:
Expand Down

0 comments on commit 9f3590b

Please sign in to comment.