from __future__ import annotations from yuxi.channels.adapters.line.doctor import LINEDoctor from yuxi.channels.adapters.line.probe import probe_line_bot from yuxi.utils.logging_config import logger class LINESetupWizard: def __init__(self, adapter=None): self._adapter = adapter def get_setup_steps(self) -> list[dict]: steps = [ { "step": 1, "title": "创建 LINE Official Account", "description": "前往 LINE Developers Console 创建 Provider 和 Channel", "action_url": "https://developers.line.biz/console/", "required": True, }, { "step": 2, "title": "获取 Channel Access Token", "description": "在 Channel Settings > Messaging API 中生成长期 Access Token", "field_key": "channel_access_token", "required": True, }, { "step": 3, "title": "获取 Channel Secret", "description": "在 Channel Settings > Basic settings 中复制 Channel Secret", "field_key": "channel_secret", "required": True, }, { "step": 4, "title": "配置 Webhook URL", "description": "在 Messaging API 设置中将 Webhook URL 设为 https:///line/callback", "action": "configure_webhook", "required": True, }, { "step": 5, "title": "启用 Webhook", "description": "开启 'Use webhook' 开关", "required": True, }, { "step": 6, "title": "禁用自动回复 (推荐)", "description": "禁用 LINE Official Account 的自动回复以避免与 Bot 冲突", "recommended": True, }, { "step": 7, "title": "配置 DM/群组策略", "description": "设置 dm_policy(open/allowlist/pairing/disabled)和 group_policy(open/allowlist/disabled)", "field_key": "dm_policy", }, { "step": 8, "title": "验证连接", "description": "测试 Token 是否有效并获取 Bot 信息", "action": "verify_connection", "field_key": "channel_access_token", }, { "step": 9, "title": "运行诊断", "description": "运行完整诊断检查,验证配置正确性和 API 可达性", "action": "run_diagnostics", }, ] if self._adapter and len(self._adapter.list_account_ids()) > 0: steps.append( { "step": 10, "title": "多账户配置", "description": "配置多个 LINE Bot 账户,通过 accounts..dm_policy 指定账户级策略", "action": "configure_multi_account", } ) return steps def _resolve_config_path(self, key: str, account_id: str | None = None) -> str: if account_id and self._adapter: account_path = f"channels.line.accounts.{account_id}.{key}" return account_path return f"channels.line.{key}" async def verify_connection(self, channel_access_token: str) -> dict: result = await probe_line_bot(channel_access_token) if result.get("status") == "ok": return { "success": True, "bot_name": result.get("display_name", ""), "bot_id": result.get("user_id", ""), "picture_url": result.get("picture_url", ""), } return {"success": False, "error": result.get("message", "Verification failed")} async def run_interactive_setup(self) -> dict: logger.info("[LINE Setup] Starting interactive setup wizard") steps = self.get_setup_steps() result = { "status": "setup_required", "channel": "line", "steps": steps, "help": "请访问 LINE Developers Console 完成配置:https://developers.line.biz/console/", } return result async def run_quick_setup(self, account_id: str | None = None) -> dict: results = { "status": "in_progress", "channel": "line", "steps_completed": [], "steps_failed": [], } if not self._adapter: results["status"] = "error" results["error"] = "No adapter available" return results token, secret = await self._adapter._resolve_token_and_secret(account_id) if not token: results["steps_failed"].append( { "step": "token_check", "error": f"Channel Access Token not configured {'(account: ' + self._resolve_config_path('channel_access_token', account_id) + ')' if account_id else ''}", } ) else: probe = await self.verify_connection(token) if probe.get("success"): results["steps_completed"].append( { "step": "token_check", "bot_name": probe.get("bot_name"), } ) if not secret: results["steps_failed"].append( { "step": "secret_check", "error": "Channel Secret not configured", } ) else: results["steps_completed"].append({"step": "secret_check"}) doctor = LINEDoctor(self._adapter) diagnostics = await doctor.run_diagnostics() for finding in diagnostics.get("findings", []): if finding["severity"] == "error": results["steps_failed"].append(finding) else: results["steps_completed"].append(finding) if not results["steps_failed"]: results["status"] = "ready" elif results["steps_completed"]: results["status"] = "partial" return results