ForcePilot/backend/package/yuxi/channels/adapters/line/setup.py
Kris a4ec94ef9d feat(line): 实现完整的 LINE 聊天适配器功能
新增 LINE 官方账号对接的全套功能,包括:
1. 基础的 Bot 探测、会话解析、消息格式化能力
2. 富媒体消息模板、快速回复、卡片指令支持
3. Webhook 签名验证、重放防护、多账户路由管理
4. 消息发送、回复、分块传输、用户绑定管理
5. 交互式配置向导与诊断工具
2026-05-12 00:45:33 +08:00

171 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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