ForcePilot/backend/package/yuxi/channels/adapters/line/setup.py

171 lines
6.3 KiB
Python
Raw Normal View History

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://<your-domain>/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_policyopen/allowlist/pairing/disabled和 group_policyopen/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.<id>.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