Skip to content

Commit e5bd2bc

Browse files
committed
NixOS compatibility patches for botright 0.5.1
- Remove solver.install() call (hcaptcha_challenger 0.18+ removed it) - Add PLAYWRIGHT_BROWSERS_PATH chromium detection for NixOS - Update hcaptcha module to AgentV API (from unmerged PR Vinyzu#167) - Graceful hcaptcha_solver init when GEMINI_API_KEY is missing - Bump version to 0.5.1.nixos1
1 parent 0299b6e commit e5bd2bc

4 files changed

Lines changed: 40 additions & 75 deletions

File tree

botright/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
from .modules.faker import Faker
33
from .modules.proxy_manager import ProxyManager
44

5-
VERSION = "0.5.1"
5+
VERSION = "0.5.1.nixos1"
66

77
__all__ = ["Botright", "Faker", "ProxyManager", "VERSION"]

botright/botright.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from typing import Any, Dict, List, Optional
88

99
import browsers
10-
import hcaptcha_challenger as solver
1110
import loguru
1211
from async_class import AsyncObject
1312
from chrome_fingerprints import AsyncFingerprintGenerator
@@ -21,7 +20,10 @@
2120

2221
logging.getLogger("websockets").setLevel(logging.WARNING)
2322
logging.getLogger("httpx").setLevel(logging.WARNING)
24-
loguru.logger.disable("hcaptcha_challenger")
23+
try:
24+
loguru.logger.disable("hcaptcha_challenger")
25+
except Exception:
26+
pass
2527

2628

2729
class Botright(AsyncObject):
@@ -77,8 +79,6 @@ async def __ainit__(
7779
use_undetected_playwright (bool, optional): Whether to use undetected_playwright . EXPERIMENTAL (TEMP). Defaults to False.
7880
"""
7981

80-
# Init local-side of the ModelHub
81-
solver.install(upgrade=True)
8282
# Starting Playwright
8383
if use_undetected_playwright:
8484
# (TODO: TEMP)
@@ -199,6 +199,15 @@ def get_browser_engine() -> browsers.Browser:
199199
Raises:
200200
EnvironmentError: If no Chromium based browser is found on the system.
201201
"""
202+
# NixOS: use Playwright bundled chromium via PLAYWRIGHT_BROWSERS_PATH
203+
pw_browsers = os.environ.get("PLAYWRIGHT_BROWSERS_PATH", "")
204+
if pw_browsers:
205+
import glob
206+
matches = glob.glob(os.path.join(pw_browsers, "chromium-*/chrome-linux*/chrome"))
207+
if matches:
208+
return {"browser_type": "chromium", "path": matches[0],
209+
"display_name": "Playwright Chromium", "version": "bundled"}
210+
202211
# Ungoogled Chromium preferred (most stealthy)
203212
if chromium := browsers.get("chromium"):
204213
return chromium

botright/modules/hcaptcha.py

Lines changed: 22 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,48 @@
11
from __future__ import annotations
22

3-
from pathlib import Path
43
from typing import TYPE_CHECKING, Optional
54

6-
from hcaptcha_challenger.agents import AgentT
5+
from hcaptcha_challenger.agent import AgentV, AgentConfig
6+
from hcaptcha_challenger.models import ChallengeSignal
77

88
if TYPE_CHECKING:
99
from botright.extended_typing import BrowserContext, Page
1010

11-
tmp_dir = Path(__file__).parent.joinpath("tmp_dir")
12-
1311

1412
class hCaptcha:
1513
def __init__(self, browser: BrowserContext, page: Page) -> None:
16-
"""
17-
Initialize an hCaptcha solver.
18-
19-
Args:
20-
browser (BrowserContext): The Playwright browser context to use.
21-
page (Page): The Playwright page where hCaptcha challenges will be solved.
22-
"""
2314
self.browser = browser
2415
self.page = page
25-
26-
self.retry_times = 8
27-
self.hcaptcha_agent = AgentT.from_page(page=page, tmp_dir=tmp_dir, self_supervised=True)
16+
self.hcaptcha_agent = AgentV(page=page, agent_config=AgentConfig())
2817

2918
async def mock_captcha(self, rq_data: str) -> None:
30-
"""
31-
Mock hCaptcha requests by intercepting network requests to getcaptcha.
32-
33-
Args:
34-
rq_data (str): The data required for mocking the hCaptcha request.
35-
36-
This method mocks the hCaptcha request and captures the generated hCaptcha token.
37-
"""
38-
3919
async def mock_json(route, request):
40-
41-
payload = {**request.post_data_json, "rqdata": rq_data, "hl": "en"} if rq_data else request.post_data_json
42-
response = await self.page.request.post(request.url, form=payload, headers=request.headers)
20+
payload = (
21+
{**request.post_data_json, "rqdata": rq_data, "hl": "en"}
22+
if rq_data
23+
else request.post_data_json
24+
)
25+
response = await self.page.request.post(
26+
request.url, form=payload, headers=request.headers
27+
)
4328
await route.fulfill(response=response)
4429

4530
await self.page.route("https://hcaptcha.com/getcaptcha/**", mock_json)
4631

4732
async def solve_hcaptcha(self, rq_data: Optional[str] = None) -> Optional[str]:
48-
"""
49-
Solve an hCaptcha challenge.
50-
51-
Args:
52-
rq_data (Optional[str]): Additional data required for solving the hCaptcha challenge.
53-
54-
Returns:
55-
Optional[str]: The hCaptcha token if successfully solved; otherwise, None.
56-
57-
This method captures the hCaptcha token by logging and mocking hCaptcha requests, then simulates clicking the
58-
hCaptcha checkbox to solve the challenge.
59-
"""
60-
# Mocking Captcha Request
6133
if rq_data:
6234
await self.mock_captcha(rq_data)
63-
# Clicking Captcha Checkbox
64-
await self.hcaptcha_agent.handle_checkbox()
65-
66-
for pth in range(1, self.retry_times):
67-
result = await self.hcaptcha_agent.execute()
68-
if result == self.hcaptcha_agent.status.CHALLENGE_BACKCALL:
69-
await self.page.wait_for_timeout(500)
70-
fl = self.page.frame_locator(self.hcaptcha_agent.HOOK_CHALLENGE)
71-
await fl.locator("//div[@class='refresh button']").click()
72-
elif result == self.hcaptcha_agent.status.CHALLENGE_SUCCESS:
73-
if self.hcaptcha_agent.cr:
74-
captcha_token: str = self.hcaptcha_agent.cr.generated_pass_UUID
75-
return captcha_token
76-
77-
return f"Exceeded maximum retry times of {self.retry_times}"
78-
79-
async def get_hcaptcha(self, site_key: Optional[str] = "00000000-0000-0000-0000-000000000000", rq_data: Optional[str] = None) -> Optional[str]:
80-
"""
81-
Get an hCaptcha token for a specific site.
82-
83-
Args:
84-
site_key (Optional[str]): The site key for the hCaptcha challenge (default is a demo site key).
85-
rq_data (Optional[str]): Additional data required for solving the hCaptcha challenge.
86-
87-
Returns:
88-
Optional[str]: The hCaptcha token if successfully obtained; otherwise, None.
89-
90-
This method opens a new page, navigates to a specified hCaptcha demo page with the given site key, and
91-
solves the hCaptcha challenge to obtain the token.
92-
"""
35+
await self.hcaptcha_agent.robotic_arm.click_checkbox()
36+
result = await self.hcaptcha_agent.wait_for_challenge()
37+
if result == ChallengeSignal.SUCCESS and self.hcaptcha_agent.cr_list:
38+
return self.hcaptcha_agent.cr_list[-1].generated_pass_UUID
39+
return None
40+
41+
async def get_hcaptcha(
42+
self,
43+
site_key: str = "00000000-0000-0000-0000-000000000000",
44+
rq_data: Optional[str] = None,
45+
) -> Optional[str]:
9346
page = await self.browser.new_page()
9447
await page.goto(f"https://accounts.hcaptcha.com/demo?sitekey={site_key}")
9548
return await page.solve_hcaptcha(rq_data=rq_data)

botright/playwright_mock/page.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ def __init__(self, page: PlaywrightPage, browser: BrowserContext, faker: Faker):
9797
else:
9898
self._keyboard = Keyboard(page.keyboard, self)
9999
self.cdp: Optional[PlaywrightCDPSession] = None
100-
self.hcaptcha_solver = hcaptcha.hCaptcha(browser, self)
100+
try:
101+
self.hcaptcha_solver = hcaptcha.hCaptcha(browser, self)
102+
except Exception:
103+
self.hcaptcha_solver = None
101104
self.recaptcha_solver = AsyncChallenger(self)
102105

103106
# Aliases

0 commit comments

Comments
 (0)