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)
|