from __future__ import annotations from typing import Any from yuxi.channels.adapters.bluebubbles.config import BlueBubblesConfig from yuxi.channels.adapters.bluebubbles.setup_core import validate_credentials, validate_server_url class BlueBubblesSetupWizard: def __init__(self): self._config = BlueBubblesConfig() self._steps: list[dict[str, Any]] = [] self._current_step = 0 self._completed_steps: set[str] = set() @property def steps(self) -> list[dict[str, Any]]: return [ { "id": "server_url", "title": "BlueBubbles 服务器地址", "description": "输入 BlueBubbles 服务器的 URL 地址", "config_key": "server_url", "required": True, }, { "id": "password", "title": "服务器密码", "description": "输入 BlueBubbles 服务器的密码", "config_key": "password", "required": True, "sensitive": True, }, { "id": "dm_policy", "title": "私聊策略", "description": "选择私聊消息的处理策略", "config_key": "dm_policy", "options": ["open", "pairing", "allowlist", "blocklist"], "default": "pairing", }, { "id": "group_policy", "title": "群组策略", "description": "选择群组消息的处理策略", "config_key": "group_policy", "options": ["open", "pairing", "allowlist", "blocklist"], "default": "allowlist", }, { "id": "streaming_mode", "title": "流式输出模式", "description": "选择消息流式输出的方式", "config_key": "streaming_mode", "options": ["off", "partial", "block"], "default": "partial", }, { "id": "tapback_enabled", "title": "Tapback 反应", "description": "是否启用 Tapback 消息反应功能", "config_key": "tapback_enabled", "type": "bool", "default": True, }, { "id": "ai_vision_enabled", "title": "AI 视觉分析", "description": "是否启用图片 AI 视觉分析功能", "config_key": "ai_vision_enabled", "type": "bool", "default": False, }, { "id": "webhook_secret", "title": "Webhook 密钥", "description": "设置 Webhook 签名验证密钥(留空则随机生成)", "config_key": "webhook_secret", "required": False, "sensitive": True, }, ] async def apply_config(self, config_updates: dict[str, Any]) -> dict[str, Any]: results: dict[str, Any] = {"success": True, "validations": {}} for key, value in config_updates.items(): if hasattr(self._config, key): setattr(self._config, key, value) self._completed_steps.add(key) if "server_url" in config_updates: validation = await validate_server_url(config_updates["server_url"]) results["validations"]["server_url"] = validation if "server_url" in config_updates and "password" in config_updates: validation = await validate_credentials( config_updates["server_url"], config_updates["password"], ) results["validations"]["credentials"] = validation if "webhook_secret" in config_updates and not config_updates["webhook_secret"]: import secrets self._config.webhook_secret = secrets.token_hex(32) results["webhook_secret_generated"] = True return results def get_config_summary(self) -> dict[str, Any]: return { "server_url": self._config.server_url, "dm_policy": self._config.dm_policy, "group_policy": self._config.group_policy, "streaming_mode": self._config.streaming_mode, "tapback_enabled": self._config.tapback_enabled, "ai_vision_enabled": self._config.ai_vision_enabled, "webhook_path": self._config.webhook_path, "completed_steps": list(self._completed_steps), "total_steps": len(self.steps), } def to_config_dict(self) -> dict[str, Any]: return self._config.model_dump() def get_next_step(self) -> dict[str, Any] | None: for step in self.steps: if step["id"] not in self._completed_steps: return step return None def mark_step_complete(self, step_id: str) -> None: self._completed_steps.add(step_id) @property def is_complete(self) -> bool: required_steps = {s["id"] for s in self.steps if s.get("required")} return required_steps.issubset(self._completed_steps) async def run_setup_wizard( server_url: str = "", password: str = "", dm_policy: str = "pairing", group_policy: str = "allowlist", streaming_mode: str = "partial", ) -> dict[str, Any]: wizard = BlueBubblesSetupWizard() config_updates: dict[str, Any] = {} if server_url: config_updates["server_url"] = server_url if password: config_updates["password"] = password if dm_policy: config_updates["dm_policy"] = dm_policy if group_policy: config_updates["group_policy"] = group_policy if streaming_mode: config_updates["streaming_mode"] = streaming_mode result = await wizard.apply_config(config_updates) return { "config": wizard.to_config_dict(), "validations": result.get("validations", {}), "complete": wizard.is_complete, "summary": wizard.get_config_summary(), }