Kaynağa Gözat

fix: #6035 百家号有头浏览器增加登录和验证码检测等待逻辑

ethanfly 4 gün önce
ebeveyn
işleme
497e0eed7e
1 değiştirilmiş dosya ile 188 ekleme ve 41 silme
  1. 188 41
      server/python/platforms/baijiahao.py

+ 188 - 41
server/python/platforms/baijiahao.py

@@ -348,6 +348,97 @@ class BaijiahaoPublisher(BasePublisher):
         
         return {'need_captcha': False, 'captcha_type': ''}
 
+    async def _check_login_resolved(self) -> bool:
+        """检查用户是否已完成登录(页面不再停留在登录页)"""
+        try:
+            current_url = self.page.url
+            for indicator in self.login_indicators:
+                if indicator in current_url:
+                    return False
+            # 检查传统登录弹窗
+            login_selectors = [
+                'text="请登录"',
+                'text="登录后继续"',
+                '[class*="login-dialog"]',
+            ]
+            for selector in login_selectors:
+                try:
+                    loc = self.page.locator(selector).first
+                    if await loc.count() > 0 and await loc.is_visible():
+                        return False
+                except:
+                    pass
+            return True
+        except:
+            return False
+
+    async def _check_captcha_resolved(self) -> bool:
+        """检查用户是否已完成验证码验证"""
+        try:
+            # 先用传统方式检查(速度快,无需 API 调用)
+            captcha_result = await self.check_captcha()
+            if not captcha_result['need_captcha']:
+                # 传统方式未检测到验证码,再用 AI 确认
+                ai_captcha = await self.ai_check_captcha()
+                if not ai_captcha['has_captcha']:
+                    return True
+            return False
+        except:
+            return False
+
+    async def _wait_for_user_resolve(
+        self,
+        check_fn,
+        timeout: int = 300,
+        poll_interval: int = 5,
+        prompt: str = "",
+    ) -> bool:
+        """
+        有头浏览器模式下,等待用户手动解决验证码/登录问题。
+
+        Args:
+            check_fn: 异步函数,返回 True 表示问题已解决
+            timeout: 超时时间(秒),默认 5 分钟
+            poll_interval: 轮询间隔(秒)
+            prompt: 展示给用户的提示信息
+
+        Returns:
+            True 表示用户已成功解决,False 表示超时
+        """
+        import time
+
+        if prompt:
+            print(f"[{self.platform_name}] {prompt}", flush=True)
+
+        start_time = time.time()
+        attempt = 0
+        while time.time() - start_time < timeout:
+            attempt += 1
+            elapsed = int(time.time() - start_time)
+            remaining = timeout - elapsed
+            print(
+                f"[{self.platform_name}] 等待用户操作... ({elapsed}s/{timeout}s, 剩余 {remaining}s)",
+                flush=True,
+            )
+            self.report_progress(
+                12,
+                f"等待用户操作中... (已等待 {elapsed}s)",
+            )
+
+            resolved = await check_fn()
+            if resolved:
+                print(f"[{self.platform_name}] 用户操作完成(第 {attempt} 次检测)", flush=True)
+                await asyncio.sleep(2)  # 额外等待页面稳定
+                return True
+
+            await asyncio.sleep(poll_interval)
+
+        print(
+            f"[{self.platform_name}] 等待用户操作超时 ({timeout}s)",
+            flush=True,
+        )
+        return False
+
     async def _ai_analyze_upload_state(self, screenshot_base64: str = None) -> dict:
         """
         使用 AI 识别当前上传状态,返回:
@@ -807,65 +898,121 @@ class BaijiahaoPublisher(BasePublisher):
         # 检查是否跳转到登录页
         current_url = self.page.url
         print(f"[{self.platform_name}] 当前页面: {current_url}")
-        
+
         for indicator in self.login_indicators:
             if indicator in current_url:
+                if not self.headless:
+                    # 有头浏览器模式:等待用户手动完成登录
+                    print(f"[{self.platform_name}] 有头模式检测到登录跳转,等待用户手动登录...")
+                    self.report_progress(12, "检测到需要登录,请在浏览器中手动完成登录...")
+                    login_resolved = await self._wait_for_user_resolve(
+                        check_fn=self._check_login_resolved,
+                        timeout=300,
+                        prompt="请在打开的浏览器中完成登录",
+                    )
+                    if login_resolved:
+                        current_url = self.page.url
+                        print(f"[{self.platform_name}] 用户已完成登录,当前页面: {current_url}")
+                        break  # 登录已解决,继续发布流程
+                    else:
+                        screenshot_base64 = await self.capture_screenshot()
+                        return PublishResult(
+                            success=False,
+                            platform=self.platform_name,
+                            error="等待用户登录超时(5分钟),请重试",
+                            need_captcha=True,
+                            captcha_type='login',
+                            screenshot_base64=screenshot_base64,
+                            page_url=current_url,
+                            status='need_captcha'
+                        )
+                else:
+                    screenshot_base64 = await self.capture_screenshot()
+                    return PublishResult(
+                        success=False,
+                        platform=self.platform_name,
+                        error="Cookie 已过期,需要重新登录",
+                        need_captcha=True,
+                        captcha_type='login',
+                        screenshot_base64=screenshot_base64,
+                        page_url=current_url,
+                        status='need_captcha'
+                    )
+
+        # 检查验证码(有头模式下等待用户手动解决,无头模式下直接返回)
+        captcha_detected = False
+        captcha_type = ''
+
+        # 使用 AI 检查验证码
+        ai_captcha = await self.ai_check_captcha()
+        if ai_captcha['has_captcha']:
+            captcha_detected = True
+            captcha_type = ai_captcha['captcha_type']
+            print(f"[{self.platform_name}] AI检测到验证码: {captcha_type}", flush=True)
+
+        # AI 未检测到时再用传统方式检查
+        if not captcha_detected:
+            captcha_result = await self.check_captcha()
+            if captcha_result['need_captcha']:
+                captcha_detected = True
+                captcha_type = captcha_result['captcha_type']
+
+        if captcha_detected:
+            if not self.headless:
+                # 有头浏览器模式:等待用户手动完成验证码
+                print(f"[{self.platform_name}] 有头模式检测到验证码({captcha_type}),等待用户手动解决...")
+                self.report_progress(12, f"检测到验证码,请在浏览器中手动完成验证...")
+                captcha_resolved = await self._wait_for_user_resolve(
+                    check_fn=self._check_captcha_resolved,
+                    timeout=300,
+                    prompt="请在打开的浏览器中完成验证码验证",
+                )
+                if not captcha_resolved:
+                    screenshot_base64 = await self.capture_screenshot()
+                    return PublishResult(
+                        success=False,
+                        platform=self.platform_name,
+                        error=f"等待用户完成验证码超时(5分钟),请重试",
+                        need_captcha=True,
+                        captcha_type=captcha_type,
+                        screenshot_base64=screenshot_base64,
+                        page_url=current_url,
+                        status='need_captcha'
+                    )
+                print(f"[{self.platform_name}] 用户已完成验证码验证,继续发布流程")
+            else:
                 screenshot_base64 = await self.capture_screenshot()
                 return PublishResult(
                     success=False,
                     platform=self.platform_name,
-                    error="Cookie 已过期,需要重新登录",
+                    error=f"检测到{captcha_type}验证码,需要使用有头浏览器完成验证",
                     need_captcha=True,
-                    captcha_type='login',
+                    captcha_type=captcha_type,
                     screenshot_base64=screenshot_base64,
                     page_url=current_url,
                     status='need_captcha'
                 )
         
-        # 使用 AI 检查验证码
-        ai_captcha = await self.ai_check_captcha()
-        if ai_captcha['has_captcha']:
-            print(f"[{self.platform_name}] AI检测到验证码: {ai_captcha['captcha_type']}", flush=True)
-            screenshot_base64 = await self.capture_screenshot()
-            return PublishResult(
-                success=False,
-                platform=self.platform_name,
-                error=f"检测到{ai_captcha['captcha_type']}验证码,需要使用有头浏览器完成验证",
-                need_captcha=True,
-                captcha_type=ai_captcha['captcha_type'],
-                screenshot_base64=screenshot_base64,
-                page_url=current_url,
-                status='need_captcha'
-            )
-        
-        # 传统方式检查验证码
-        captcha_result = await self.check_captcha()
-        if captcha_result['need_captcha']:
-            screenshot_base64 = await self.capture_screenshot()
-            return PublishResult(
-                success=False,
-                platform=self.platform_name,
-                error=f"需要{captcha_result['captcha_type']}验证码,请使用有头浏览器完成验证",
-                need_captcha=True,
-                captcha_type=captcha_result['captcha_type'],
-                screenshot_base64=screenshot_base64,
-                page_url=current_url,
-                status='need_captcha'
-            )
-        
         self.report_progress(15, "正在选择视频文件...")
         
         # 等待页面加载完成
         await asyncio.sleep(2)
         
-        # 关闭可能的弹窗
+        # 关闭可能的弹窗(有头模式下使用更保守的选择器,避免关闭用户需要交互的验证码弹窗)
         try:
-            close_buttons = [
-                'button:has-text("我知道了")',
-                'button:has-text("知道了")',
-                '[class*="close"]',
-                '[class*="modal-close"]',
-            ]
+            if self.headless:
+                close_buttons = [
+                    'button:has-text("我知道了")',
+                    'button:has-text("知道了")',
+                    '[class*="close"]',
+                    '[class*="modal-close"]',
+                ]
+            else:
+                # 有头模式:只关闭明确的提示性弹窗,不触碰可能含验证码的对话框
+                close_buttons = [
+                    'button:has-text("我知道了")',
+                    'button:has-text("知道了")',
+                ]
             for btn_selector in close_buttons:
                 try:
                     btn = self.page.locator(btn_selector).first