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

98 lines
3.7 KiB
Python

from __future__ import annotations
import asyncio
from yuxi.channels.adapters.line.probe import probe_line_bot
from yuxi.utils.logging_config import logger
class LINEGateway:
"""Per-account Gateway 启动管理器,支持独立 connect/monitor/Webhook 注册。"""
def __init__(self, adapter):
self._adapter = adapter
self._account_states: dict[str, dict] = {}
self._monitor_tasks: dict[str, asyncio.Task] = {}
async def start_account(self, account_id: str, token: str, secret: str) -> bool:
from yuxi.channels.adapters.line.send import LINESender
state = self._account_states.setdefault(account_id, {"status": "starting", "sender": None, "error": None})
try:
sender = LINESender(token)
await sender.__aenter__()
info = await sender.get_bot_info()
if not info:
await sender.__aexit__()
state["status"] = "error"
state["error"] = "Failed to get bot info"
return False
state["sender"] = sender
state["status"] = "connected"
state["info"] = {
"display_name": info.get("displayName", ""),
"user_id": info.get("userId", ""),
"picture_url": info.get("pictureUrl", ""),
}
logger.info(f"[LINE Gateway] account '{account_id}' started: {state['info']['display_name']}")
return True
except Exception as e:
state["status"] = "error"
state["error"] = str(e)
logger.error(f"[LINE Gateway] failed to start account '{account_id}': {e}")
return False
async def stop_account(self, account_id: str) -> None:
if account_id in self._monitor_tasks:
self._monitor_tasks[account_id].cancel()
del self._monitor_tasks[account_id]
state = self._account_states.pop(account_id, None)
if state and state.get("sender"):
try:
await state["sender"].__aexit__()
except Exception:
pass
logger.info(f"[LINE Gateway] account '{account_id}' stopped")
async def probe_account(self, token: str) -> dict:
return await probe_line_bot(token)
def get_account_state(self, account_id: str) -> dict | None:
return self._account_states.get(account_id)
def list_accounts(self) -> list[str]:
return list(self._account_states.keys())
async def start_monitor(self, account_id: str, interval: float = 60.0) -> None:
if account_id in self._monitor_tasks:
return
async def _monitor_loop():
while True:
try:
state = self._account_states.get(account_id)
if not state or state["status"] != "connected":
break
sender = state.get("sender")
if sender:
info = await sender.get_bot_info()
if not info:
state["status"] = "error"
state["error"] = "Bot info fetch failed during monitor"
break
except asyncio.CancelledError:
break
except Exception as e:
logger.warning(f"[LINE Gateway] monitor error for '{account_id}': {e}")
await asyncio.sleep(interval)
task = asyncio.ensure_future(_monitor_loop())
self._monitor_tasks[account_id] = task
async def stop_all(self) -> None:
for account_id in list(self._monitor_tasks.keys()):
await self.stop_account(account_id)