新增 LINE 官方账号对接的全套功能,包括: 1. 基础的 Bot 探测、会话解析、消息格式化能力 2. 富媒体消息模板、快速回复、卡片指令支持 3. Webhook 签名验证、重放防护、多账户路由管理 4. 消息发送、回复、分块传输、用户绑定管理 5. 交互式配置向导与诊断工具
171 lines
6.3 KiB
Python
171 lines
6.3 KiB
Python
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_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.<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
|