WechatOnCloud/bridge/woc_bridge/messaging/login_guard.py
Kris c45282f094 feat: 新增好友自动通过、UI自动化能力与多分辨率适配
- 新增登录状态守卫后台任务
- 新增好友申请自动通过规则引擎
- 新增多分辨率UI配置与模板资源
- 新增消息拉取复合游标支持
- 优化发送队列与UI自动化逻辑
- 新增批量发送日志与错误处理
- 优化Docker镜像构建与ptrace初始化
- 新增联系人名称缓存预热
2026-07-17 18:10:31 +08:00

128 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""微信登录状态守卫:后台检测登录状态,提供同步属性与事件通知。
独立后台协程定期调用 detect_login_state(),维护 asyncio.Event 标记当前
登录状态。状态变化时记录日志(避免每轮都输出噪音)。
设计目标:
- 后台任务(如 FriendRequestWatcher在执行 UI 操作前检查 is_logged_in
未登录时跳过操作,等待登录恢复后自然从上次位置继续处理。
- 游标 / 状态不推进,登录后 DB 中积累的数据自然被重新读取,不丢数据。
- 不替代路由层的登录守卫routes/send.py 等已有 detect_login_state 前置检查),
仅用于后台长轮询任务的登录前提保护。
"""
from __future__ import annotations
import asyncio
import logging
from typing import Optional
logger = logging.getLogger("woc-bridge")
class LoginGuard:
"""微信登录状态守卫。
Attributes:
_xdotool: XdotoolDriver 实例(调用 detect_login_state
_check_interval: 检测间隔秒数(默认 5.0s
_logged_in_event: asyncio.Eventset 表示当前已登录
_watcher: 后台检测协程
_current_state: 最近一次检测到的登录状态字符串
_last_logged_in: 上次是否登录(用于检测状态变化)
"""
def __init__(
self,
xdotool_driver,
check_interval: float = 5.0,
) -> None:
self._xdotool = xdotool_driver
self._check_interval = check_interval
self._logged_in_event: asyncio.Event = asyncio.Event()
self._watcher: Optional[asyncio.Task] = None
self._current_state: str = "unknown"
self._last_logged_in: bool = False
@property
def is_logged_in(self) -> bool:
"""当前是否已登录(同步属性,取最近一次检测结果)。"""
return self._logged_in_event.is_set()
@property
def current_state(self) -> str:
"""最近一次检测到的登录状态字符串。"""
return self._current_state
async def wait_for_login(self, timeout: Optional[float] = None) -> bool:
"""等待登录状态变为已登录。
Args:
timeout: 超时秒数None 表示无限等待
Returns:
True 表示已登录False 表示超时仍未登录
"""
if self._logged_in_event.is_set():
return True
try:
await asyncio.wait_for(
self._logged_in_event.wait(), timeout=timeout
)
return True
except asyncio.TimeoutError:
return False
async def start(self) -> None:
"""启动后台检测协程。"""
if self._watcher is None or self._watcher.done():
self._watcher = asyncio.create_task(self._check_loop())
logger.info("LoginGuard 已启动 (check_interval=%.1fs)", self._check_interval)
async def stop(self) -> None:
"""停止后台检测协程。"""
if self._watcher is not None and not self._watcher.done():
self._watcher.cancel()
try:
await self._watcher
except asyncio.CancelledError:
pass
self._watcher = None
logger.info("LoginGuard 已停止")
async def _check_loop(self) -> None:
"""后台检测循环:定期调用 detect_login_state 并维护事件状态。"""
while True:
try:
state = await self._xdotool.detect_login_state()
logged_in = (state == "logged_in")
# 仅在状态变化时记录日志,避免每轮噪音
if logged_in != self._last_logged_in:
if logged_in:
logger.info(
"[LoginGuard] 微信已登录 (state=%s),恢复后台任务",
state,
)
else:
logger.warning(
"[LoginGuard] 微信未登录 (state=%s),暂停需要登录的后台任务",
state,
)
self._last_logged_in = logged_in
if logged_in:
self._logged_in_event.set()
else:
self._logged_in_event.clear()
self._current_state = state
except asyncio.CancelledError:
raise
except Exception as e:
logger.warning("[LoginGuard] 登录状态检测异常: %s", e)
self._logged_in_event.clear()
self._current_state = "error"
await asyncio.sleep(self._check_interval)