ForcePilot/backend/package/yuxi/channel/extensions/kakaotalk/gateway.py
Kris 8b66218d9b feat(channel): 添加 KakaoTalk 渠道扩展
新增 KakaoTalk 渠道扩展,支持在 Yuxi 平台中集成 KakaoTalk 即时通讯渠道。

包含以下功能模块:
- bot: Bot 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- card_builder: KakaoTalk 卡片消息构建
- quick_reply: 快捷回复处理
- types: 类型定义
2026-05-21 11:12:31 +08:00

105 lines
3.7 KiB
Python

from __future__ import annotations
import asyncio
import logging
from yuxi.channel.extensions.kakaotalk.bot import KakaoTalkBotClient
from yuxi.channel.extensions.kakaotalk.webhook import _handler as _webhook_handler
logger = logging.getLogger(__name__)
class KakaoTalkGatewayAdapter:
def __init__(self):
self._webhook = _webhook_handler
self._running = False
self._account_cache: dict = {}
async def start(self, ctx) -> object:
account_id = getattr(ctx, "account_id", "default")
config = getattr(ctx, "config", {}) or {}
account = self._resolve_account(ctx)
self._account_cache[account_id] = account
admin_key = account.get("admin_key", "")
bot_id = account.get("bot_id", "")
if not admin_key or not bot_id:
logger.warning("KakaoTalk gateway start: admin_key or bot_id not configured for account %s", account_id)
self._running = True
queue = getattr(ctx, "queue", asyncio.Queue())
return queue
try:
bot = KakaoTalkBotClient(admin_key=admin_key)
alive = await bot.probe()
if alive:
logger.info("KakaoTalk bot probe successful for account %s (bot_id=%s)", account_id, bot_id)
else:
logger.warning("KakaoTalk bot probe failed for account %s", account_id)
except Exception:
logger.exception("KakaoTalk bot probe error for account %s", account_id)
self._webhook.start()
self._webhook.configure(config)
self._running = True
queue = getattr(ctx, "queue", asyncio.Queue())
return queue
async def stop(self, ctx) -> None:
self._running = False
self._webhook.stop()
self._account_cache.clear()
logger.info("KakaoTalk gateway stopped")
async def probe(self, account: dict) -> bool:
admin_key = account.get("admin_key", "")
if not admin_key:
return False
try:
bot = KakaoTalkBotClient(admin_key=admin_key)
return await bot.probe()
except Exception:
return False
@staticmethod
def _resolve_account(ctx) -> dict:
account_id = getattr(ctx, "account_id", "default")
config = getattr(ctx, "config", {}) or {}
kt_cfg = config.get("channels", {}).get("kakaotalk", {})
if not isinstance(kt_cfg, dict):
kt_cfg = {}
accounts = kt_cfg.get("accounts", {})
if not isinstance(accounts, dict):
accounts = {}
account_raw = accounts.get(account_id, {})
if not isinstance(account_raw, dict):
account_raw = {}
admin_key = account_raw.get("admin_key") or kt_cfg.get("admin_key", "")
rest_api_key = account_raw.get("rest_api_key") or kt_cfg.get("rest_api_key", "")
bot_id = account_raw.get("bot_id") or kt_cfg.get("bot_id", "")
return {
"account_id": account_id,
"admin_key": admin_key,
"rest_api_key": rest_api_key,
"bot_id": bot_id,
"channel_name": account_raw.get("channel_name", kt_cfg.get("channel_name", "")),
"name": account_raw.get("name", account_id),
"dm_policy": account_raw.get("dm_policy", kt_cfg.get("dm_policy", "pairing")),
"allow_from": account_raw.get("allow_from", kt_cfg.get("allow_from", [])),
"skill_server_path": account_raw.get(
"skill_server_path", kt_cfg.get("skill_server_path", "/kakaotalk/skill")
),
"text_chunk_limit": account_raw.get(
"text_chunk_limit", kt_cfg.get("text_chunk_limit", 1000)
),
}