You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

107 lines
3.7 KiB

"""微信消息监听器 — 顶层编排"""
from __future__ import annotations
import logging
import threading
from collections.abc import Callable
from ..config import Config
from ..ui.wechat import WeChatWindow
from ..ui.conversation import ConversationList
from ..ui.message import MessageList
from .event import MessageEvent
from .polling import PollingEngine
logger = logging.getLogger(__name__)
class WeChatWatcher:
"""微信消息监听器:整合窗口定位、会话切换、消息轮询"""
def __init__(self, config: Config | None = None) -> None:
self.config = config or Config()
self._wechat = WeChatWindow(self.config)
self._conv_list: ConversationList | None = None
self._msg_list: MessageList | None = None
self._poller: PollingEngine | None = None
self._thread: threading.Thread | None = None
self._stop_event = threading.Event()
self._callbacks: list[Callable[[MessageEvent], None]] = []
# ------------------------------------------------------------------
# 回调
# ------------------------------------------------------------------
def on_message(self, callback: Callable[[MessageEvent], None]) -> None:
"""注册消息回调(可在 start 前多次调用)"""
self._callbacks.append(callback)
# ------------------------------------------------------------------
# 初始化
# ------------------------------------------------------------------
def connect(self) -> bool:
"""查找并连接微信窗口"""
win = self._wechat.find_window()
if win is None:
logger.error("无法连接微信窗口,请确保微信已登录并保持窗口可见")
return False
self._conv_list = ConversationList(win, self.config)
self._msg_list = MessageList(win)
self._poller = PollingEngine(self._msg_list, self.config)
for cb in self._callbacks:
self._poller.on_message(cb)
logger.info("微信监听器初始化完成")
return True
# ------------------------------------------------------------------
# 启动 / 停止
# ------------------------------------------------------------------
def start(self, conversation: str = "", daemon: bool = True) -> bool:
"""在后台线程中启动消息轮询"""
if self._poller is None:
if not self.connect():
return False
self._stop_event.clear()
self._thread = threading.Thread(
target=self._poller.start_loop,
args=(conversation, self._stop_event),
daemon=daemon,
name="wechat-watcher",
)
self._thread.start()
logger.info("监听线程已启动")
return True
def stop(self) -> None:
"""停止监听"""
self._stop_event.set()
if self._poller:
self._poller.stop()
if self._thread and self._thread.is_alive():
self._thread.join(timeout=5)
logger.info("监听已停止")
# ------------------------------------------------------------------
# 会话切换
# ------------------------------------------------------------------
def switch_conversation(self, name: str) -> bool:
"""切换到指定会话"""
if self._conv_list is None:
logger.error("监听器未初始化,请先调用 connect()")
return False
return self._conv_list.click_conversation(name)
def search_and_switch(self, keyword: str) -> bool:
"""搜索并切换到指定会话"""
if self._conv_list is None:
logger.error("监听器未初始化,请先调用 connect()")
return False
return self._conv_list.search_conversation(keyword)