From 0b59bc4a0af27e089ddf86279559e86fccf5d054 Mon Sep 17 00:00:00 2001 From: liuwei Date: Thu, 7 May 2026 15:47:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E6=95=9B864=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=80=81=E5=88=87=E6=8D=A2=E5=BC=82=E5=B8=B8=E4=B8=8E=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=99=AA=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wechat_ipad/providers/server_864/base.py | 20 ++++++++- wechat_ipad/providers/server_864/runtime.py | 50 ++++++++++++++++++++- wechat_ipad/providers/server_864/user.py | 13 +++++- 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/wechat_ipad/providers/server_864/base.py b/wechat_ipad/providers/server_864/base.py index 667f5ff..ba3b88d 100644 --- a/wechat_ipad/providers/server_864/base.py +++ b/wechat_ipad/providers/server_864/base.py @@ -64,7 +64,19 @@ class Server864APIClientBase: params=merged_params, json=json_body, ) as response: - payload = await response.json(content_type=None) + try: + payload = await response.json(content_type=None) + except Exception: + raw_text = await response.text() + # 864 少数接口会直接返回纯文本而不是标准 DTO: + # 1. 当前已在 `LogOut` 链路里碰到这种情况,旧逻辑会先炸 JSON 解析,再把真正状态信息吞掉; + # 2. 对 2xx 响应来说,这通常只是“接口风格不统一”,不应该直接视为致命协议错误; + # 3. 因此这里统一包成兼容 DTO,保留原始文本给上层按需处理。 + payload = { + "Code": 200 if response.status < 400 else response.status, + "Data": None, + "Text": str(raw_text or "").strip(), + } return self._validate_payload(payload) async def _request_data( @@ -96,6 +108,12 @@ class Server864APIClientBase: return payload message = str(payload.get("Text") or payload.get("Message") or "server_864 请求失败").strip() + # 864 某些登录接口会用非 200 编码表达“当前 key 已经在线”: + # 1. 对二维码申请链路来说,这更接近一种状态回执,而不是硬失败; + # 2. 如果这里直接抛异常,上层就会把“已经在线”误判成致命错误并退出线程; + # 3. 因此先保留 payload 原样放行,让 runtime 再决定是复用现有登录态还是继续走扫码。 + if "该链接已绑定微信号" in message and "在线状态良好" in message: + return payload lowered_message = message.lower() if any(keyword in lowered_message for keyword in ("重新登录", "已退出登录", "离线", "账号需要重新登录")): raise UserLoggedOut(message) diff --git a/wechat_ipad/providers/server_864/runtime.py b/wechat_ipad/providers/server_864/runtime.py index ec8cad1..027cadb 100644 --- a/wechat_ipad/providers/server_864/runtime.py +++ b/wechat_ipad/providers/server_864/runtime.py @@ -195,7 +195,55 @@ class Server864RuntimeMixin: callback_name="on_login_qr_update", ) - uuid, url = await self.get_qr_code(print_qr=True, login_qr_api=login_qr_api, login_way=login_way) + try: + uuid, url = await self.get_qr_code(print_qr=True, login_qr_api=login_qr_api, login_way=login_way) + except Exception as e: + qr_error_message = str(e).strip() + if "该链接已绑定微信号" in qr_error_message and "在线状态良好" in qr_error_message: + # 当 864 服务端明确提示“当前 key 已在线”时,优先尝试直接复用现有登录态: + # 1. 这通常出现在切换二维码模式过快、旧连接尚未完全退出的瞬间; + # 2. 若此时直接抛错,线程会把“已经在线”误判成获取二维码失败; + # 3. 因此这里先转成“尝试接管现有会话”,成功后直接收口,不再强制继续申请新二维码。 + identity_ready = await self._refresh_identity_from_profile(logger=logger) + if identity_ready: + ipad_config["wxid"] = self.wxid + ipad_config["login_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + self._save_runtime_state( + state_path=state_path, + state_payload={"wxid": self.wxid, "login_time": ipad_config["login_time"]}, + logger=logger, + ) + await self._safe_callback( + on_login_qr_cleared, + { + "status": "logged_in", + "status_text": "864 服务端已存在可用登录态,已自动复用当前会话", + "provider_name": "server_864", + "provider_stage": "logged_in", + "connection_ready": True, + "login_required": False, + }, + logger=logger, + callback_name="on_login_qr_cleared", + ) + return + await self._safe_callback( + on_login_qr_update, + { + "status": "unavailable", + "status_text": self._normalize_login_runtime_message(qr_error_message), + "login_source": "fresh_qr", + "provider_name": "server_864", + "provider_stage": "status_unavailable", + "connection_ready": False, + "login_required": True, + "login_qr_api": login_qr_api, + "login_way": login_way, + }, + logger=logger, + callback_name="on_login_qr_update", + ) + raise scan_url = f"http://weixin.qq.com/x/{uuid}" if uuid else "" effective_time = 0 raw_state = 0 diff --git a/wechat_ipad/providers/server_864/user.py b/wechat_ipad/providers/server_864/user.py index c33c9e2..9e434af 100644 --- a/wechat_ipad/providers/server_864/user.py +++ b/wechat_ipad/providers/server_864/user.py @@ -1,5 +1,7 @@ from loguru import logger +from wechat_ipad.errors import UserLoggedOut + from wechat_ipad.providers.server_864.base import Server864APIClientBase @@ -59,6 +61,13 @@ class UserMixin(Server864APIClientBase): return True await self.get_profile() return True - except Exception as e: - logger.error("server_864 is_logged_in:{}", e) + except UserLoggedOut as e: + # “未登录 / 需要重新登录”是 864 登录引导中的常规状态,不应该长期污染 error 日志: + # 1. 当前首页会主动引导扫码,这类返回本质上只是“当前还没登录完成”; + # 2. 若仍按 error 记录,运维排查时很难分清真正异常和正常登录态切换; + # 3. 因此这里降级为 info,只保留可读状态文本。 + logger.info("server_864 is_logged_in:{}", e) + return False + except Exception as e: + logger.warning("server_864 is_logged_in:{}", e) return False