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