- 新增登录状态守卫后台任务 - 新增好友申请自动通过规则引擎 - 新增多分辨率UI配置与模板资源 - 新增消息拉取复合游标支持 - 优化发送队列与UI自动化逻辑 - 新增批量发送日志与错误处理 - 优化Docker镜像构建与ptrace初始化 - 新增联系人名称缓存预热
128 lines
4.6 KiB
Python
128 lines
4.6 KiB
Python
"""微信登录状态守卫:后台检测登录状态,提供同步属性与事件通知。
|
||
|
||
独立后台协程定期调用 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.Event,set 表示当前已登录
|
||
_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)
|