From 9dbc108298ef221675f6b8ad5ca3239691f047c3 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Wed, 1 Oct 2025 00:22:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(maizone):=20=E5=BB=BA=E7=AB=8B=E5=A4=9A?= =?UTF-8?q?=E7=BA=A7=E5=9B=9E=E9=80=80=E6=9C=BA=E5=88=B6=E4=BB=A5=E4=BF=9D?= =?UTF-8?q?=E9=9A=9C=E7=A9=BA=E9=97=B4Cookie=E8=8E=B7=E5=8F=96=E7=9A=84?= =?UTF-8?q?=E5=81=A5=E5=A3=AE=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 该提交旨在从根本上解决MaiZone插件因单一Cookie来源不稳定而导致功能中断的问题。通过引入一个具有优先级的多级回退策略,显著提高了服务的可用性和容错能力。 核心变更如下: - **引入健壮的获取策略**:重构了Cookie的获取逻辑,建立了一条清晰的降级路径。现在系统会优先通过最可靠的Napcat HTTP服务获取,若失败则尝试读取本地文件缓存,最后才调用可能超时的Adapter API。这确保了即使部分服务不可用,插件仍有很大概率正常工作。 - **优化插件生命周期**:修正了插件加载时的初始化流程,将服务注册和后台任务启动合并到单一的`on_plugin_loaded`方法中,消除了潜在的竞态条件,确保了监控和定时任务总能被正确启动。 - **提升操作容忍度**:将Napcat适配器中`get_cookies`动作的超时阈值放宽至40秒,为网络延迟或不稳定的情况提供了更充足的缓冲时间,减少了因过早超时而造成的失败。 - **细化过程日志**:在整个Cookie获取和QZone服务调用链中增加了详细的上下文日志,使得在出现问题时能够快速定位失败环节和具体原因,极大地简化了未来的故障排查工作。 --- .../built_in/maizone_refactored/plugin.py | 24 ++++----- .../services/cookie_service.py | 35 +++++++------ .../services/qzone_service.py | 52 ++++++++++++++----- .../napcat_adapter_plugin/src/send_handler.py | 18 ++++--- 4 files changed, 79 insertions(+), 50 deletions(-) diff --git a/src/plugins/built_in/maizone_refactored/plugin.py b/src/plugins/built_in/maizone_refactored/plugin.py index de644c31b..4ea543f68 100644 --- a/src/plugins/built_in/maizone_refactored/plugin.py +++ b/src/plugins/built_in/maizone_refactored/plugin.py @@ -88,25 +88,27 @@ class MaiZoneRefactoredPlugin(BasePlugin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) async def on_plugin_loaded(self): + """插件加载完成后的回调,初始化服务并启动后台任务""" + # --- 注册权限节点 --- await permission_api.register_permission_node( "plugin.maizone.send_feed", "是否可以使用机器人发送QQ空间说说", "maiZone", False ) await permission_api.register_permission_node( "plugin.maizone.read_feed", "是否可以使用机器人读取QQ空间说说", "maiZone", True ) - # 创建所有服务实例 + + # --- 创建并注册所有服务实例 --- content_service = ContentService(self.get_config) image_service = ImageService(self.get_config) cookie_service = CookieService(self.get_config) reply_tracker_service = ReplyTrackerService() - # 使用已创建的 reply_tracker_service 实例 qzone_service = QZoneService( self.get_config, content_service, image_service, cookie_service, - reply_tracker_service, # 传入已创建的实例 + reply_tracker_service, ) scheduler_service = SchedulerService(self.get_config, qzone_service) monitor_service = MonitorService(self.get_config, qzone_service) @@ -115,18 +117,12 @@ class MaiZoneRefactoredPlugin(BasePlugin): register_service("reply_tracker", reply_tracker_service) register_service("get_config", self.get_config) - # 保存服务引用以便后续启动 - self.scheduler_service = scheduler_service - self.monitor_service = monitor_service + logger.info("MaiZone重构版插件服务已注册。") - logger.info("MaiZone重构版插件已加载,服务已注册。") - - async def on_plugin_loaded(self): - """插件加载完成后的回调,启动异步服务""" - if hasattr(self, "scheduler_service") and hasattr(self, "monitor_service"): - asyncio.create_task(self.scheduler_service.start()) - asyncio.create_task(self.monitor_service.start()) - logger.info("MaiZone后台任务已启动。") + # --- 启动后台任务 --- + asyncio.create_task(scheduler_service.start()) + asyncio.create_task(monitor_service.start()) + logger.info("MaiZone后台监控和定时任务已启动。") def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: return [ diff --git a/src/plugins/built_in/maizone_refactored/services/cookie_service.py b/src/plugins/built_in/maizone_refactored/services/cookie_service.py index b4aedf322..e3692f883 100644 --- a/src/plugins/built_in/maizone_refactored/services/cookie_service.py +++ b/src/plugins/built_in/maizone_refactored/services/cookie_service.py @@ -113,31 +113,32 @@ class CookieService: async def get_cookies(self, qq_account: str, stream_id: Optional[str]) -> Optional[Dict[str, str]]: """ 获取Cookie,按以下顺序尝试: - 1. Adapter API - 2. HTTP备用端点 - 3. 本地文件缓存 + 1. HTTP备用端点 (更稳定) + 2. 本地文件缓存 + 3. Adapter API (作为最后手段) """ - # 1. 尝试从Adapter获取 - cookies = await self._get_cookies_from_adapter(stream_id) - if cookies: - logger.info("成功从Adapter获取Cookie。") - self._save_cookies_to_file(qq_account, cookies) - return cookies - - # 2. 尝试从HTTP备用端点获取 - logger.warning("从Adapter获取Cookie失败,尝试使用HTTP备用地址。") + # 1. 尝试从HTTP备用端点获取 + logger.info(f"开始尝试从HTTP备用地址获取 {qq_account} 的Cookie...") cookies = await self._get_cookies_from_http() if cookies: - logger.info("成功从HTTP备用地址获取Cookie。") + logger.info(f"成功从HTTP备用地址为 {qq_account} 获取Cookie。") self._save_cookies_to_file(qq_account, cookies) return cookies - # 3. 尝试从本地文件加载 - logger.warning("从HTTP备用地址获取Cookie失败,尝试加载本地缓存。") + # 2. 尝试从本地文件加载 + logger.warning(f"从HTTP备用地址获取 {qq_account} 的Cookie失败,尝试加载本地缓存。") cookies = self._load_cookies_from_file(qq_account) if cookies: - logger.info("成功从本地文件加载缓存的Cookie。") + logger.info(f"成功从本地文件为 {qq_account} 加载缓存的Cookie。") return cookies - logger.error("所有Cookie获取方法均失败。") + # 3. 尝试从Adapter获取 (作为最后的备用方案) + logger.warning(f"从本地缓存加载 {qq_account} 的Cookie失败,最后尝试使用Adapter API。") + cookies = await self._get_cookies_from_adapter(stream_id) + if cookies: + logger.info(f"成功从Adapter API为 {qq_account} 获取Cookie。") + self._save_cookies_to_file(qq_account, cookies) + return cookies + + logger.error(f"为 {qq_account} 获取Cookie的所有方法均失败。请确保Napcat HTTP服务或Adapter连接至少有一个正常工作,或存在有效的本地Cookie文件。") return None diff --git a/src/plugins/built_in/maizone_refactored/services/qzone_service.py b/src/plugins/built_in/maizone_refactored/services/qzone_service.py index 09e6f5e53..40c0d424a 100644 --- a/src/plugins/built_in/maizone_refactored/services/qzone_service.py +++ b/src/plugins/built_in/maizone_refactored/services/qzone_service.py @@ -409,8 +409,9 @@ class QZoneService: cookie_dir.mkdir(exist_ok=True) cookie_file_path = cookie_dir / f"cookies-{qq_account}.json" + # 优先尝试通过Napcat HTTP服务获取最新的Cookie try: - # 使用HTTP服务器方式获取Cookie + logger.info("尝试通过Napcat HTTP服务获取Cookie...") host = self.get_config("cookie.http_fallback_host", "172.20.130.55") port = self.get_config("cookie.http_fallback_port", "9999") napcat_token = self.get_config("cookie.napcat_token", "") @@ -421,23 +422,43 @@ class QZoneService: parsed_cookies = { k.strip(): v.strip() for k, v in (p.split("=", 1) for p in cookie_str.split("; ") if "=" in p) } - with open(cookie_file_path, "wb") as f: - f.write(orjson.dumps(parsed_cookies)) - logger.info(f"Cookie已更新并保存至: {cookie_file_path}") + # 成功获取后,异步写入本地文件作为备份 + try: + with open(cookie_file_path, "wb") as f: + f.write(orjson.dumps(parsed_cookies)) + logger.info(f"通过Napcat服务成功更新Cookie,并已保存至: {cookie_file_path}") + except Exception as e: + logger.warning(f"保存Cookie到文件时出错: {e}") return parsed_cookies + else: + logger.warning("通过Napcat服务未能获取有效Cookie。") - # 如果HTTP获取失败,尝试读取本地文件 - if cookie_file_path.exists(): - with open(cookie_file_path, "rb") as f: - return orjson.loads(f.read()) - return None except Exception as e: - logger.error(f"更新或加载Cookie时发生异常: {e}") - return None + logger.warning(f"通过Napcat HTTP服务获取Cookie时发生异常: {e}。将尝试从本地文件加载。") - async def _fetch_cookies_http(self, host: str, port: str, napcat_token: str) -> Optional[Dict]: + # 如果通过服务获取失败,则尝试从本地文件加载 + logger.info("尝试从本地Cookie文件加载...") + if cookie_file_path.exists(): + try: + with open(cookie_file_path, "rb") as f: + cookies = orjson.loads(f.read()) + logger.info(f"成功从本地文件加载Cookie: {cookie_file_path}") + return cookies + except Exception as e: + logger.error(f"从本地文件 {cookie_file_path} 读取或解析Cookie失败: {e}") + else: + logger.warning(f"本地Cookie文件不存在: {cookie_file_path}") + + logger.error("所有获取Cookie的方式均失败。") + return None + + async def _fetch_cookies_http(self, host: str, port: int, napcat_token: str) -> Optional[Dict]: """通过HTTP服务器获取Cookie""" - url = f"http://{host}:{port}/get_cookies" + # 从配置中读取主机和端口,如果未提供则使用传入的参数 + final_host = self.get_config("cookie.http_fallback_host", host) + final_port = self.get_config("cookie.http_fallback_port", port) + url = f"http://{final_host}:{final_port}/get_cookies" + max_retries = 5 retry_delay = 1 @@ -481,14 +502,19 @@ class QZoneService: async def _get_api_client(self, qq_account: str, stream_id: Optional[str]) -> Optional[Dict]: cookies = await self.cookie_service.get_cookies(qq_account, stream_id) if not cookies: + logger.error("获取API客户端失败:未能获取到Cookie。请检查Napcat连接是否正常,或是否存在有效的本地Cookie文件。") return None p_skey = cookies.get("p_skey") or cookies.get("p_skey".upper()) if not p_skey: + logger.error(f"获取API客户端失败:Cookie中缺少关键的 'p_skey'。Cookie内容: {cookies}") return None gtk = self._generate_gtk(p_skey) uin = cookies.get("uin", "").lstrip("o") + if not uin: + logger.error(f"获取API客户端失败:Cookie中缺少关键的 'uin'。Cookie内容: {cookies}") + return None async def _request(method, url, params=None, data=None, headers=None): final_headers = {"referer": f"https://user.qzone.qq.com/{uin}", "origin": "https://user.qzone.qq.com"} diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py b/src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py index 4e4aa3e10..c8bc267af 100644 --- a/src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py +++ b/src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py @@ -185,9 +185,13 @@ class SendHandler: logger.info(f"执行适配器命令: {action}") - # 直接向Napcat发送命令并获取响应 - response_task = asyncio.create_task(self.send_message_to_napcat(action, params)) - response = await response_task + # 根据action决定处理方式 + if action == "get_cookies": + # 对于get_cookies,我们需要一个更长的超时时间 + response = await self.send_message_to_napcat(action, params, timeout=40.0) + else: + # 对于其他命令,使用默认超时 + response = await self.send_message_to_napcat(action, params) # 发送响应回MaiBot await self.send_adapter_command_response(raw_message_base, response, request_id) @@ -196,6 +200,8 @@ class SendHandler: logger.info(f"适配器命令 {action} 执行成功") else: logger.warning(f"适配器命令 {action} 执行失败,napcat返回:{str(response)}") + # 无论成功失败,都记录下完整的响应内容以供调试 + logger.debug(f"适配器命令 {action} 的完整响应: {response}") except Exception as e: logger.error(f"处理适配器命令时发生错误: {e}") @@ -583,7 +589,7 @@ class SendHandler: }, ) - async def send_message_to_napcat(self, action: str, params: dict) -> dict: + async def send_message_to_napcat(self, action: str, params: dict, timeout: float = 20.0) -> dict: request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) @@ -595,9 +601,9 @@ class SendHandler: try: await connection.send(payload) - response = await get_response(request_uuid) + response = await get_response(request_uuid, timeout=timeout) # 使用传入的超时时间 except TimeoutError: - logger.error("发送消息超时,未收到响应") + logger.error(f"发送消息超时({timeout}秒),未收到响应: action={action}, params={params}") return {"status": "error", "message": "timeout"} except Exception as e: logger.error(f"发送消息失败: {e}")