# /// # node_manager.py # 描述:节点管理服务,负责与WXAUTO-HTTP-API节点通信 # 作者:AI Generated # 创建日期:2026-04-05 # /// import asyncio from typing import Dict, List, Optional, Any import httpx from services.log_service import log_service from database_service import get_db_service class NodeStatus: ACTIVE = "active" INACTIVE = "inactive" ERROR = "error" class Node: def __init__(self, node_id: str, name: str, api_url: str, api_key: str, description: str = "", group: str = "default"): self.node_id = node_id self.name = name self.api_url = api_url.rstrip("/") self.api_key = api_key self.description = description self.group = group self.enabled = True self.status = NodeStatus.INACTIVE self.wechat_status = "online" self.is_healthy = False def to_dict(self) -> Dict[str, Any]: return { "node_id": self.node_id, "name": self.name, "api_url": self.api_url, "enabled": self.enabled, "description": self.description, "group": self.group, "status": self.status, "wechat_status": self.wechat_status, "is_healthy": self.is_healthy } class NodeManager: def __init__(self): self.nodes: Dict[str, Node] = {} async def add_node(self, node_id: str, name: str, api_url: str, api_key: str, description: str = "", group: str = "default") -> bool: if node_id in self.nodes: return False self.nodes[node_id] = Node(node_id, name, api_url, api_key, description, group) await self.check_node_status(node_id) return True async def remove_node(self, node_id: str) -> bool: if node_id not in self.nodes: return False del self.nodes[node_id] return True async def get_node(self, node_id: str) -> Optional[Node]: return self.nodes.get(node_id) async def get_all_nodes(self) -> List[Node]: return list(self.nodes.values()) async def get_enabled_nodes(self) -> List[Node]: return [n for n in self.nodes.values() if n.enabled] async def get_nodes_by_group(self, group: str) -> List[Node]: return [n for n in self.nodes.values() if n.group == group] def to_config(self) -> List[Dict[str, Any]]: return [{ "node_id": n.node_id, "name": n.name, "api_url": n.api_url, "api_key": n.api_key, "enabled": n.enabled, "description": n.description, "group": n.group } for n in self.nodes.values()] async def update_node(self, node_id: str, **kwargs) -> bool: node = self.nodes.get(node_id) if not node: return False for key, value in kwargs.items(): if hasattr(node, key): setattr(node, key, value) if "api_url" in kwargs or "api_key" in kwargs: await self.check_node_status(node_id) return True async def check_node_status(self, node_id: str) -> str: node = self.nodes.get(node_id) if not node or not node.enabled: return NodeStatus.INACTIVE try: async with httpx.AsyncClient(timeout=5.0) as client: response = await client.get( f"{node.api_url}/health", headers={"X-API-Key": node.api_key} ) if response.status_code == 200: node.status = NodeStatus.ACTIVE else: node.status = NodeStatus.ERROR except Exception: node.status = NodeStatus.ERROR return node.status async def is_node_healthy(self, node_id: str) -> tuple[bool, str]: node = self.nodes.get(node_id) if not node or not node.enabled: return False, "Node not found or disabled" api_status = await self.check_node_status(node_id) if api_status != NodeStatus.ACTIVE: return False, f"API status: {api_status}" return True, "OK" async def call_node_api(self, node_id: str, endpoint: str, method: str = "GET", data: Optional[Dict] = None) -> Dict[str, Any]: node = self.nodes.get(node_id) if not node or not node.enabled: return {"success": False, "error": f"Node '{node_id}' not found or disabled"} url = f"{node.api_url}{endpoint}" headers = {"X-API-Key": node.api_key} try: async with httpx.AsyncClient(timeout=10.0) as client: if method.upper() == "GET": response = await client.get(url, headers=headers) elif method.upper() == "POST": response = await client.post(url, headers=headers, json=data) else: return {"success": False, "error": f"Unsupported method: {method}"} if response.status_code == 200: return {"success": True, "data": response.json()} else: return {"success": False, "error": f"HTTP {response.status_code}", "url": url} except Exception as e: return {"success": False, "error": str(e), "url": url} async def send_message(self, node_id: str, who: str, msg: str, msg_type: str = "text") -> Dict[str, Any]: node = self.nodes.get(node_id) await self.call_node_api(node_id, "/api/wechat/initialize", method="POST") result = await self.call_node_api( node_id, "/api/message/send", method="POST", data={"receiver": who, "message": msg} ) if node: node.wechat_status = "online" if result.get("success") else "offline" db_service = get_db_service() if db_service: db_service.update_node(node_id, wechat_status=node.wechat_status) return result async def send_message_to_group(self, node_id: str, group_name: str, msg: str, msg_type: str = "text") -> Dict[str, Any]: node = self.nodes.get(node_id) await self.call_node_api(node_id, "/api/wechat/initialize", method="POST") result = await self.call_node_api( node_id, "/api/message/send", method="POST", data={"receiver": group_name, "message": msg} ) if node: node.wechat_status = "online" if result.get("success") else "offline" db_service = get_db_service() if db_service: db_service.update_node(node_id, wechat_status=node.wechat_status) return result async def get_friends_list(self, node_id: str) -> Dict[str, Any]: return await self.call_node_api(node_id, "/api/friend/list", method="GET") async def get_groups_list(self, node_id: str) -> Dict[str, Any]: return await self.call_node_api(node_id, "/api/group/list", method="GET") async def get_messages(self, node_id: str, chat_name: str, limit: int = 20) -> Dict[str, Any]: return await self.call_node_api( node_id, "/api/message/get-history", method="POST", data={"chat_name": chat_name, "save_pic": False, "save_video": False, "save_file": False, "save_voice": False} ) async def reset_wechat_status(self, node_id: str) -> Dict[str, Any]: node = self.nodes.get(node_id) if not node: return {"success": False, "error": f"Node '{node_id}' not found"} await self.call_node_api(node_id, "/api/wechat/initialize", method="POST") result = await self.call_node_api( node_id, "/api/message/send", method="POST", data={"receiver": "文件传输助手", "message": "WXAuto Center 状态检测消息"} ) if result.get("success"): node.wechat_status = "online" else: node.wechat_status = "offline" db_service = get_db_service() if db_service: db_service.update_node(node_id, wechat_status=node.wechat_status) return result node_manager = NodeManager()