From 537a3d49e15f438997e0f986a6fe34f5b3564547 Mon Sep 17 00:00:00 2001 From: liuwei Date: Thu, 7 May 2026 15:28:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5GetLoginStatus=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA864=E7=99=BB=E5=BD=95=E6=80=81=E5=88=A4=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wechat_ipad/providers/server_864/login.py | 58 ++++++++++++++++++++- wechat_ipad/providers/server_864/runtime.py | 25 +++++++++ wechat_ipad/providers/server_864/user.py | 18 +++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/wechat_ipad/providers/server_864/login.py b/wechat_ipad/providers/server_864/login.py index 51623e1..a277613 100644 --- a/wechat_ipad/providers/server_864/login.py +++ b/wechat_ipad/providers/server_864/login.py @@ -7,6 +7,54 @@ from wechat_ipad.providers.server_864.base import Server864APIClientBase class LoginMixin(Server864APIClientBase): """864 登录相关接口。""" + @staticmethod + def _normalize_online_text(value) -> str: + """把 864 各种登录态字段压平成便于判断的字符串。""" + return str(value or "").strip().lower() + + def _is_online_from_login_status_payload(self, data: dict | None) -> bool: + """根据 `GetLoginStatus` 的返回结构判断当前是否在线。 + + 设计说明: + 1. 864 不同版本对在线态字段命名并不统一,可能是 `loginState/status/state/isLogin` 中的任意一种; + 2. 这里集中做一次宽松识别,避免上层 runtime 到处散落同类判断; + 3. 后续如果 864 新版本再补字段,只需要在这里扩展,不必改多处业务逻辑。 + """ + payload = dict(data or {}) + normalized_login_state = self._normalize_online_text( + self._pick_first(payload, "loginState", "LoginState", "status_text", "statusText") + ) + normalized_status = self._normalize_online_text( + self._pick_first(payload, "status", "Status", "state_text", "stateText") + ) + state_value = self._pick_first(payload, "state", "State") + login_flag = self._pick_first(payload, "isLogin", "IsLogin", "online", "Online", "isOnline", "IsOnline") + + if normalized_login_state in {"online", "已登录", "在线"}: + return True + if normalized_status in {"online", "已登录", "在线"}: + return True + if isinstance(login_flag, bool): + return login_flag + if str(login_flag or "").strip().lower() in {"true", "1", "online"}: + return True + try: + normalized_state_value = int(state_value or 0) + except (TypeError, ValueError): + normalized_state_value = 0 + return normalized_state_value in {1, 2} + + def _extract_login_identity_from_status(self, data: dict | None) -> dict: + """从 `GetLoginStatus` 返回中提取尽可能多的账号身份字段。""" + payload = dict(data or {}) + return { + "wxid": str(self._pick_first(payload, "wxid", "Wxid", "UserName", "userName") or "").strip(), + "nickname": str(self._pick_first(payload, "nick_name", "nickName", "NickName", "nickname") or "").strip(), + "alias": str(self._pick_first(payload, "alias", "Alias", "wechatId", "WeChatId") or "").strip(), + "phone": str(self._pick_first(payload, "mobile", "Mobile", "phone", "Phone") or "").strip(), + "signature": str(self._pick_first(payload, "signature", "Signature") or "").strip(), + } + @staticmethod def _normalize_login_way(login_way: str) -> str: """标准化 864 `GetLoginQrCodeNewX` 的 way 参数。""" @@ -146,12 +194,20 @@ class LoginMixin(Server864APIClientBase): async def get_login_status(self, auto_login: bool = True) -> dict: """获取 864 在线状态。""" - return await self._request_data( + data = await self._request_data( "get", "/login/GetLoginStatus", params={"autoLogin": str(bool(auto_login)).lower()}, timeout=20, ) + normalized = dict(data or {}) + # 把最常用的在线态判断和身份字段提前归一化: + # 1. 这样上层只要消费 `is_online` / `wxid` / `nickname` 这些统一键,不必感知原始 swagger 字段差异; + # 2. 与 `CheckLoginStatus` 一样,Dashboard 和 runtime 都能共享同一份兼容结果; + # 3. 也方便后续把 864 的不同 server 版本收敛到更薄的一层 provider 适配。 + normalized["is_online"] = self._is_online_from_login_status_payload(normalized) + normalized.update(self._extract_login_identity_from_status(normalized)) + return normalized async def log_out(self) -> bool: """退出当前 864 登录态。""" diff --git a/wechat_ipad/providers/server_864/runtime.py b/wechat_ipad/providers/server_864/runtime.py index 077feac..ec8cad1 100644 --- a/wechat_ipad/providers/server_864/runtime.py +++ b/wechat_ipad/providers/server_864/runtime.py @@ -534,6 +534,31 @@ class Server864RuntimeMixin: profile = await self.get_profile() except Exception as e: error_message = str(e).strip() + # `GetProfile` 失败时,再用 `GetLoginStatus` 补做一次登录态与身份判定: + # 1. 用户当前联调里已经确认 `GetLoginStatus` 可用,而 `GetProfile` / `GetInItStatus` 在部分阶段会直接报“该链接不存在”; + # 2. 若此时登录状态接口其实还能返回在线和账号字段,就没必要仅因资料接口异常而误判整轮登录失败; + # 3. 因此这里把它作为资料接口失败后的第一优先补偿探针,尽量保住已经完成的登录链路。 + try: + login_status = await self.get_login_status(auto_login=False) + except Exception as status_error: + logger.warning( + f"server_864 登录状态补偿探针也失败,无法确认当前账号身份: {error_message}; " + f"GetLoginStatus={status_error}" + ) + else: + if self._is_online_from_login_status_payload(login_status): + identity = self._extract_login_identity_from_status(login_status) + self.wxid = identity.get("wxid", self.wxid) + self.nickname = identity.get("nickname", self.nickname) + self.alias = identity.get("alias", self.alias) + self.phone = identity.get("phone", self.phone) + self.signature = identity.get("signature", self.signature) + if self.wxid or self.nickname: + logger.info( + "server_864 资料接口失败,但已通过 GetLoginStatus 补确认当前账号身份: " + f"wxid={self.wxid} nickname={self.nickname}" + ) + return True # 864 有些版本在消息链路可用后,资料接口仍可能短时间不可用: # 1. 此时若直接抛异常,会让“已经登录成功”的启动流程被资料查询反向拖垮; # 2. 但如果当前连 `wxid/nickname` 都没有,就不能再假装“已经有可用身份”; diff --git a/wechat_ipad/providers/server_864/user.py b/wechat_ipad/providers/server_864/user.py index c71ff8b..c33c9e2 100644 --- a/wechat_ipad/providers/server_864/user.py +++ b/wechat_ipad/providers/server_864/user.py @@ -39,6 +39,24 @@ class UserMixin(Server864APIClientBase): """检查 864 当前账号是否在线。""" del wxid try: + # 优先使用 864 自己的登录状态接口判断在线态: + # 1. `GetProfile` 在某些版本里会比真实登录态更早失效,容易把“已登录但资料接口异常”误判成未登录; + # 2. 用户当前给出的 `GetLoginStatus` 正是更贴近 server 自身会话状态的一条探针; + # 3. 因此这里先走登录状态接口,只有它也无法确认时,才回退到资料接口兜底。 + login_status = await self.get_login_status(auto_login=False) + if self._is_online_from_login_status_payload(login_status): + identity = self._extract_login_identity_from_status(login_status) + if identity.get("wxid"): + self.wxid = identity["wxid"] + if identity.get("nickname"): + self.nickname = identity["nickname"] + if identity.get("alias"): + self.alias = identity["alias"] + if identity.get("phone"): + self.phone = identity["phone"] + if identity.get("signature"): + self.signature = identity["signature"] + return True await self.get_profile() return True except Exception as e: