From 2dfae177754d3512f4ebfd5207c74066cb85ea45 Mon Sep 17 00:00:00 2001 From: tankztz Date: Thu, 6 Aug 2026 18:57:56 +0800 Subject: [PATCH 1/3] fix(tencent): support current video publish page --- uploader/tencent_uploader/main.py | 39 ++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/uploader/tencent_uploader/main.py b/uploader/tencent_uploader/main.py index c053970..bd46436 100644 --- a/uploader/tencent_uploader/main.py +++ b/uploader/tencent_uploader/main.py @@ -569,12 +569,22 @@ class TencentBaseUploader(BaseVideoUploader): fi = await find_file_input() if fi is None: - # 助手落在首页:先点「发表视频」唤出编辑器与上传控件 - publish_btn = page.get_by_text("发表视频").first - if await publish_btn.count(): - await publish_btn.click() - await asyncio.sleep(3) - for _ in range(20): + # 新版视频号助手先落在首页;必须点击唯一可见的「发表视频」按钮, + # 路由切到 /platform/post/create 后上传 input 才会挂载。 + publish_buttons = page.get_by_role("button", name="发表视频", exact=True) + clicked_publish = False + for index in range(await publish_buttons.count()): + candidate = publish_buttons.nth(index) + if await candidate.is_visible(): + await candidate.click(force=True) + clicked_publish = True + break + if clicked_publish: + try: + await page.wait_for_url("**/platform/post/create", timeout=30000) + except Exception: + pass + for _ in range(45): fi = await find_file_input() if fi is not None: break @@ -717,13 +727,20 @@ class TencentBaseUploader(BaseVideoUploader): tencent_logger.warning(_msg("📭", "本视频未声明原创(页面无入口或为可选项),跳过并继续发布")) async def wait_for_upload_complete(self, page: Page) -> None: + deadline = asyncio.get_running_loop().time() + 20 * 60 while True: + if asyncio.get_running_loop().time() >= deadline: + raise TimeoutError("等待视频号上传完成超过 20 分钟") try: - publish_button = page.get_by_role("button", name="发表") - button_class = await publish_button.get_attribute("class") - if button_class and "weui-desktop-btn_disabled" not in button_class: - tencent_logger.info(_msg("🥳", "视频上传完毕")) - break + publish_button = page.locator('div.form-btns button:has-text("发表"):visible').first + if await publish_button.count(): + button_class = await publish_button.get_attribute("class") + if ( + not await publish_button.is_disabled() + and (not button_class or "weui-desktop-btn_disabled" not in button_class) + ): + tencent_logger.info(_msg("🥳", "视频上传完毕")) + break tencent_logger.info(_msg("🏃", "正在上传视频中...")) await asyncio.sleep(2) From 8ee7bcdb8ad14c9cf9cba457b562dc8094b128f7 Mon Sep 17 00:00:00 2001 From: tankztz Date: Fri, 7 Aug 2026 17:04:43 +0800 Subject: [PATCH 2/3] fix(tencent): poll delayed publish entry --- uploader/tencent_uploader/main.py | 39 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/uploader/tencent_uploader/main.py b/uploader/tencent_uploader/main.py index bd46436..1f17774 100644 --- a/uploader/tencent_uploader/main.py +++ b/uploader/tencent_uploader/main.py @@ -568,26 +568,29 @@ class TencentBaseUploader(BaseVideoUploader): return None fi = await find_file_input() - if fi is None: - # 新版视频号助手先落在首页;必须点击唯一可见的「发表视频」按钮, - # 路由切到 /platform/post/create 后上传 input 才会挂载。 - publish_buttons = page.get_by_role("button", name="发表视频", exact=True) - clicked_publish = False - for index in range(await publish_buttons.count()): - candidate = publish_buttons.nth(index) - if await candidate.is_visible(): - await candidate.click(force=True) - clicked_publish = True - break - if clicked_publish: + clicked_publish = False + for _ in range(60): + if fi is not None: + break + if not clicked_publish: + # 新版视频号助手可能先落在首页,且「发表视频」按钮异步出现。 + # 持续轮询所有可访问 button;Patchright 在当前页面上按名称精确匹配不稳定。 try: - await page.wait_for_url("**/platform/post/create", timeout=30000) + publish_buttons = await page.get_by_role("button").all() except Exception: - pass - for _ in range(45): - fi = await find_file_input() - if fi is not None: - break + publish_buttons = [] + for candidate in publish_buttons: + try: + button_text = (await candidate.inner_text()).strip() + is_visible = await candidate.is_visible() + except Exception: + continue + if "发表视频" in button_text and is_visible: + await candidate.click(force=True) + clicked_publish = True + break + fi = await find_file_input() + if fi is None: await asyncio.sleep(1) if fi is None: raise RuntimeError("未找到视频号文件上传框") From ad2419ef16faf3679b77f930386c7688c94acd9b Mon Sep 17 00:00:00 2001 From: tankztz Date: Mon, 10 Aug 2026 02:09:51 +0800 Subject: [PATCH 3/3] fix(tencent): wait for real-name verification --- tests/test_tencent_verification_dialog.py | 43 +++++++++++++++++++++++ uploader/tencent_uploader/main.py | 28 +++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 tests/test_tencent_verification_dialog.py diff --git a/tests/test_tencent_verification_dialog.py b/tests/test_tencent_verification_dialog.py new file mode 100644 index 0000000..575b270 --- /dev/null +++ b/tests/test_tencent_verification_dialog.py @@ -0,0 +1,43 @@ +import asyncio +from pathlib import Path + +from patchright.async_api import async_playwright + +from uploader.tencent_uploader.main import TencentBaseUploader + + +def test_wait_for_realtime_verification_saves_qr_and_resumes(tmp_path: Path): + async def scenario(): + async with async_playwright() as playwright: + browser = await playwright.chromium.launch(headless=True, channel="chrome") + page = await browser.new_page() + await page.set_content( + """ +
+
为保障账号安全,需用管理员微信扫码完成实名验证后才能继续发表
+
QR
+
+ """ + ) + uploader = TencentBaseUploader(publish_date=0, account_file="unused") + qr_path = tmp_path / "verification.png" + + async def dismiss_dialog(): + await page.wait_for_timeout(150) + await page.locator("div.weui-desktop-dialog__wrp").evaluate("el => el.remove()") + + dismiss_task = asyncio.create_task(dismiss_dialog()) + result = await uploader.wait_for_realtime_verification( + page, + qr_path=qr_path, + timeout_seconds=2, + poll_interval_seconds=0.05, + ) + await dismiss_task + await browser.close() + + assert result == qr_path + assert qr_path.is_file() + assert qr_path.stat().st_size > 0 + + asyncio.run(scenario()) diff --git a/uploader/tencent_uploader/main.py b/uploader/tencent_uploader/main.py index 1f17774..0720e62 100644 --- a/uploader/tencent_uploader/main.py +++ b/uploader/tencent_uploader/main.py @@ -513,6 +513,33 @@ class TencentBaseUploader(BaseVideoUploader): else: self.publish_date = 0 + async def wait_for_realtime_verification( + self, + page: Page, + qr_path: str | Path | None = None, + timeout_seconds: float = 10 * 60, + poll_interval_seconds: float = 2, + ) -> Path | None: + dialog = page.locator("div.weui-desktop-dialog__wrp:visible").filter(has_text="实名验证").first + if not await dialog.count() or not await dialog.is_visible(): + return None + + output_path = Path(qr_path) if qr_path else Path(self.account_file).with_name( + f"{Path(self.account_file).stem}_verification_qr.png" + ) + output_path.parent.mkdir(parents=True, exist_ok=True) + await dialog.screenshot(path=str(output_path)) + tencent_logger.warning(_msg("📱", f"需要管理员微信扫码完成实名验证: {output_path}")) + + deadline = asyncio.get_running_loop().time() + timeout_seconds + while await dialog.count() and await dialog.is_visible(): + if asyncio.get_running_loop().time() >= deadline: + raise TimeoutError("等待视频号管理员实名验证超时") + await asyncio.sleep(poll_interval_seconds) + + tencent_logger.success(_msg("🥳", "管理员实名验证已完成,继续发表")) + return output_path + async def set_schedule_time_tencent(self, page: Page, publish_date: datetime): label_element = page.locator("label").filter(has_text="定时").nth(1) await label_element.click() @@ -946,6 +973,7 @@ class TencentVideo(TencentBaseUploader): ) async def prepare_video_for_publish(self, page: Page) -> None: + await self.wait_for_realtime_verification(page) await self.fill_title_and_tags(page) await self.fill_description(page) await self.apply_collection(page)