Skip to content

Commit bf8c764

Browse files
authored
Merge pull request #394 from SK-415/dev
🔖 publish v1.6.0
2 parents 672d051 + a30bda8 commit bf8c764

4 files changed

Lines changed: 135 additions & 6 deletions

File tree

haruka_bot/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Config(BaseSettings):
1717
haruka_dynamic_interval: int = 0
1818
haruka_dynamic_at: bool = False
1919
haruka_screenshot_style: str = "mobile"
20+
haruka_captcha_address: str = "https://captcha-cd.ngworks.cn"
2021
haruka_dynamic_timeout: int = 30
2122
haruka_dynamic_font_source: str = "system"
2223
haruka_dynamic_font: Optional[str] = "Noto Sans CJK SC"

haruka_bot/utils/browser.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from ..config import plugin_config
1313
from .fonts_provider import fill_font
14+
from .captcha import resolve_captcha
1415

1516
_browser: Optional[Browser] = None
1617
mobile_js = Path(__file__).parent.joinpath("mobile.js")
@@ -56,11 +57,14 @@ async def get_dynamic_screenshot_mobile(dynamic_id):
5657
)
5758
try:
5859
await page.route(re.compile("^https://static.graiax/fonts/(.+)$"), fill_font)
59-
await page.goto(
60-
url,
61-
wait_until="networkidle",
62-
timeout=plugin_config.haruka_dynamic_timeout * 1000,
63-
)
60+
if plugin_config.haruka_captcha_address:
61+
page = await resolve_captcha(url,page)
62+
else:
63+
await page.goto(
64+
url,
65+
wait_until="networkidle",
66+
timeout=plugin_config.haruka_dynamic_timeout * 1000,
67+
)
6468
# 动态被删除或者进审核了
6569
if page.url == "https://m.bilibili.com/404":
6670
return None

haruka_bot/utils/captcha.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import contextlib
2+
from typing import Optional
3+
4+
import httpx
5+
from nonebot.log import logger
6+
from playwright._impl._api_structures import Position
7+
from playwright.async_api import Page, Response
8+
from pydantic import BaseModel
9+
from yarl import URL
10+
11+
from ..config import plugin_config
12+
13+
14+
class CaptchaData(BaseModel):
15+
captcha_id: str
16+
points: list[list[int]]
17+
rectangles: list[list[int]]
18+
yolo_data: list[list[int]]
19+
time: int
20+
21+
22+
class CaptchaResponse(BaseModel):
23+
code: int
24+
message: str
25+
data: Optional[CaptchaData]
26+
27+
28+
async def resolve_captcha(url: str, page: Page) -> Page:
29+
captcha_image_body = ""
30+
last_captcha_id = ""
31+
captcha_result = None
32+
33+
async def captcha_image_url_callback(response: Response):
34+
nonlocal captcha_image_body
35+
logger.debug(f"[Captcha] Get captcha image url: {response.url}")
36+
captcha_image_body = await response.body()
37+
38+
async def captcha_result_callback(response: Response):
39+
nonlocal captcha_result, last_captcha_id
40+
logger.debug(f"[Captcha] Get captcha result: {response.url}")
41+
captcha_resp = await response.text()
42+
logger.debug(f"[Captcha] Result: {captcha_resp}")
43+
if '"result": "success"' in captcha_resp:
44+
logger.success("[Captcha] 验证码 Callback 验证成功")
45+
captcha_result = True
46+
elif '"result": "click"' in captcha_resp:
47+
pass
48+
else:
49+
if last_captcha_id:
50+
logger.warning(f"[Captcha] 验证码 Callback 验证失败,正在上报:{last_captcha_id}")
51+
async with httpx.AsyncClient() as client:
52+
await client.post(
53+
f"{captcha_baseurl}/report",
54+
json={"captcha_id": last_captcha_id},
55+
)
56+
last_captcha_id = ""
57+
captcha_result = False
58+
59+
captcha_address = URL(plugin_config.haruka_captcha_address)
60+
page.on(
61+
"response",
62+
lambda response: captcha_image_url_callback(response)
63+
if response.url.startswith("https://static.geetest.com/captcha_v3/")
64+
else None,
65+
)
66+
page.on(
67+
"response",
68+
lambda response: captcha_result_callback(response)
69+
if response.url.startswith("https://api.geetest.com/ajax.php")
70+
else None,
71+
)
72+
73+
with contextlib.suppress(TimeoutError):
74+
await page.goto(
75+
url,
76+
wait_until="networkidle",
77+
timeout=plugin_config.haruka_dynamic_timeout * 1000,
78+
)
79+
80+
captcha_baseurl = f"{captcha_address.scheme}://{captcha_address.host}:{captcha_address.port}/captcha/select"
81+
while captcha_image_body or captcha_result is False:
82+
logger.warning("[Captcha] 需要人机验证,正在尝试自动解决验证码")
83+
captcha_image = await page.query_selector(".geetest_item_img")
84+
assert captcha_image
85+
captcha_size = await captcha_image.bounding_box()
86+
assert captcha_size
87+
origin_image_size = 344, 384
88+
89+
async with httpx.AsyncClient() as client:
90+
captcha_req = await client.post(
91+
f"{captcha_baseurl}/bytes",
92+
timeout=10,
93+
files={"img_file": captcha_image_body},
94+
)
95+
captcha_req = CaptchaResponse(**captcha_req.json())
96+
logger.debug(f"[Captcha] Get Resolve Result: {captcha_req}")
97+
assert captcha_req.data
98+
last_captcha_id = captcha_req.data.captcha_id
99+
if captcha_req.data:
100+
click_points: list[list[int]] = captcha_req.data.points
101+
logger.warning(f"[Captcha] 识别到 {len(click_points)} 个坐标,正在点击")
102+
# 根据原图大小和截图大小计算缩放比例,然后计算出正确的需要点击的位置
103+
for point in click_points:
104+
real_click_points = {
105+
"x": point[0] * captcha_size["width"] / origin_image_size[0],
106+
"y": point[1] * captcha_size["height"] / origin_image_size[1],
107+
}
108+
await captcha_image.click(position=Position(**real_click_points))
109+
await page.wait_for_timeout(800)
110+
captcha_image_body = ""
111+
await page.click("text=确认")
112+
geetest_up = await page.wait_for_selector(".geetest_up", state="visible")
113+
if not geetest_up:
114+
logger.warning("[Captcha] 未检测到验证码验证结果,正在重试")
115+
continue
116+
geetest_result = await geetest_up.text_content()
117+
assert geetest_result
118+
logger.debug(f"[Captcha] Geetest result: {geetest_result}")
119+
if "验证成功" in geetest_result:
120+
logger.success("[Captcha] 极验网页 Tip 验证成功")
121+
else:
122+
logger.warning("[Captcha] 极验验证失败,正在重试")
123+
124+
return page

haruka_bot/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from packaging.version import Version
22

3-
__version__ = "1.5.4"
3+
__version__ = "1.6.0"
44
VERSION = Version(__version__)

0 commit comments

Comments
 (0)