diff --git a/logs/tencent_mounted_form.html b/logs/tencent_mounted_form.html
new file mode 100644
index 0000000..fffee51
--- /dev/null
+++ b/logs/tencent_mounted_form.html
@@ -0,0 +1,32 @@
+
+
+ We're sorry but finder-helper-web doesn't work
+ properly without JavaScript enabled. Please enable it to
+ continue.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logs/tencent_note_form_debug.html b/logs/tencent_note_form_debug.html
new file mode 100644
index 0000000..0136ac3
--- /dev/null
+++ b/logs/tencent_note_form_debug.html
@@ -0,0 +1,7 @@
+===== frame[0] https://channels.weixin.qq.com/platform/post/create =====
+(不可点);
- # 首页真正可点的入口是 button.weui-desktop-btn。
- publish_entry = page.locator("button.weui-desktop-btn", has_text="发表视频").first
+ # 注意:「发表视频」按钮在首页的 post-card.html iframe 里,不在顶层文档,
+ # 必须用 frame_locator 进入该 iframe 才能点到;用 page.locator 只搜顶层会找不到,
+ # 导致走兜底直接 goto post/create,表单 iframe 只挂载空壳(Vue 不渲染)。
+ clicked = False
try:
- await publish_entry.wait_for(state="visible", timeout=30000)
+ publish_entry = page.frame_locator('iframe[src*="post-card"]').locator(
+ "button.weui-desktop-btn", has_text="发表视频"
+ ).first
+ await publish_entry.wait_for(state="visible", timeout=60000)
await publish_entry.click()
+ clicked = True
except Exception:
- # 兜底:按钮没点到时退回老逻辑直接跳转(可能仍是空壳,但保持向后兼容)
+ # 兜底:遍历所有 frame 找「发表视频」按钮
+ for fr in page.frames:
+ try:
+ entry = fr.locator("button.weui-desktop-btn", has_text="发表视频").first
+ if await entry.count() and await entry.is_visible():
+ await entry.click()
+ clicked = True
+ break
+ except Exception:
+ continue
+ if not clicked:
+ # 最后兜底:按钮没点到时退回老逻辑直接跳转(可能仍是空壳,但保持向后兼容)
+ tencent_logger.warning(_msg("⚠️", "未在首页找到「发表视频」入口,退回直接打开发布页"))
await page.goto(TENCENT_UPLOAD_URL, timeout=120000, wait_until="domcontentloaded")
+ else:
+ tencent_logger.info(_msg("🖱️", "已点击首页「发表视频」入口"))
try:
await page.wait_for_url("**/platform/post/create", timeout=120000)
except Exception:
pass
+ # 追踪:点击后各 frame 状态,确认表单是否真正挂载
+ try:
+ frame_info = []
+ for fr in page.frames:
+ try:
+ fi = await fr.locator('input[type="file"]').count()
+ frame_info.append(f"{fr.url[:45]}(file_input={fi})")
+ except Exception:
+ frame_info.append(f"{fr.url[:45]}(err)")
+ tencent_logger.info(_msg("🔍", f"打开发布页后 frame 状态: {frame_info}"))
+ except Exception:
+ pass
# 上传表单在 micro/content/post/create 这个 iframe 里,domcontentloaded 时它还是空的。
# 不等网络静默就去找 input[type=file],会误报「未找到视频号文件上传框」——
# 失败截图上左栏渲染正常、主内容区一片空白,看起来完全不像加载没完成。
try:
- await page.wait_for_load_state("networkidle", timeout=30000)
+ await page.wait_for_load_state("networkidle", timeout=60000)
except Exception:
pass # 静默不了就算了,下面还有重试兜底
@@ -735,7 +768,7 @@ class TencentBaseUploader(BaseVideoUploader):
fi = await find_file_input()
clicked_publish = False
- for _ in range(60):
+ for _ in range(120):
if fi is not None:
break
if not clicked_publish:
@@ -1288,33 +1321,13 @@ class TencentNote(TencentBaseUploader):
self.image_paths = normalized_image_paths
async def switch_to_note_mode(self, page: Page) -> None:
- """切换到图文发布模式。
+ """切换到图文发布模式(已废弃)。
- 视频号发布页(/platform/post/create)默认是视频模式,图文需切到图片模式。
- 优先点「发表图片」入口,再尝试页面内「图片/图文」tab,找不到则按当前页面继续。
+ 图文发布有独立创建页 platform/post/finderNewLifeCreate,
+ open_note_page 已直接导航到图文表单,不需要再在视频表单里切换模式。
+ 保留此方法仅为向后兼容,实际不再被调用。
"""
- for label in ["发表图片", "发布图片", "图片动态"]:
- try:
- entry = page.locator("button", has_text=label).first
- if await entry.count():
- await entry.wait_for(state="visible", timeout=8000)
- await entry.click()
- await page.wait_for_timeout(1500)
- tencent_logger.info(_msg("🖼️", f"已点击「{label}」切换到图文模式"))
- return
- except Exception:
- continue
- for text in ["图片", "图文"]:
- try:
- tab = page.locator('[role="tab"]', has_text=text).first
- if await tab.count():
- await tab.click(timeout=5000)
- await page.wait_for_timeout(1000)
- tencent_logger.info(_msg("🖼️", f"已点击「{text}」tab 切换到图文模式"))
- return
- except Exception:
- continue
- tencent_logger.warning(_msg("⚠️", "未找到明确的图文入口,按当前页面继续尝试上传图片"))
+ pass
async def upload_note_images(self, page: Page) -> None:
"""上传图文图片(优先 accept 含 image 的 file input,兜底任意 file input)。"""
@@ -1366,10 +1379,36 @@ class TencentNote(TencentBaseUploader):
await self.apply_original_statement(page)
async def upload_note_content(self, page: Page) -> None:
- await self.switch_to_note_mode(page)
+ # 图文发布有独立创建页(finderNewLifeCreate),open_note_page 已直接导航到图文表单,
+ # 不需要再在视频表单里切换模式
await self.upload_note_images(page)
await self.prepare_note_for_publish(page)
+ async def open_note_page(self, page: Page) -> None:
+ """打开图文发布页(独立路径,不需要在视频表单里切换模式)。
+
+ 图文发布有独立创建页 platform/post/finderNewLifeCreate,直接导航即可。
+ 网络慢时表单挂载较慢,拉长等待时间。
+ """
+ await page.goto(TENCENT_NOTE_CREATE_URL, timeout=180000, wait_until="domcontentloaded")
+ # cookie 失效时前端 JS 会跳转到登录页, 提前发现并报明确的错误
+ try:
+ await page.wait_for_url("**/login.html**", timeout=10000)
+ raise RuntimeError("视频号 cookie 已失效(被跳转到登录页),请重新扫码登录后再发布")
+ except TimeoutError:
+ pass # 10 秒内未跳转, 正常
+ except RuntimeError:
+ raise
+ except Exception:
+ pass
+ if any("open.weixin.qq.com/connect/qrconnect" in fr.url for fr in page.frames):
+ raise RuntimeError("视频号 cookie 已失效(被跳转到登录页),请重新扫码登录后再发布")
+ # 等表单挂载,网络慢时拉长等待
+ try:
+ await page.wait_for_load_state("networkidle", timeout=60000)
+ except Exception:
+ pass
+
async def upload(self, playwright: Playwright) -> None:
tencent_logger.info(_msg("🧍", "小人先检查 cookie、图文图片和发布时间"))
await self.validate_upload_args()
@@ -1381,7 +1420,7 @@ class TencentNote(TencentBaseUploader):
try:
page = await context.new_page()
- await self.open_upload_page(page)
+ await self.open_note_page(page)
tencent_logger.info(_msg("🏃", f"小人开始搬运图文,共 {len(self.image_paths)} 张图片"))
await self.upload_note_content(page)