ForcePilot/backend/package/yuxi/channel/extensions/kakaotalk/monitor.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

110 lines
3.6 KiB
Python

from __future__ import annotations
import json
import logging
import hashlib
from datetime import datetime, timezone
from yuxi.channel.extensions.kakaotalk.types import SkillRequest
from yuxi.channel.message.models import MessageType, PeerInfo, UnifiedMessage
from yuxi.channel.routing.models import PeerKind
logger = logging.getLogger(__name__)
class KakaoTalkMonitor:
def parse_webhook_body(self, raw_body: bytes) -> dict:
try:
return json.loads(raw_body)
except json.JSONDecodeError:
logger.warning("KakaoTalk webhook: invalid JSON body")
return {}
def parse_skill_request(self, payload: dict) -> SkillRequest | None:
if not payload.get("userRequest"):
return None
try:
return SkillRequest.from_dict(payload)
except Exception:
logger.exception("KakaoTalk SkillRequest parse error")
return None
def build_unified_message(
self,
skill_request: SkillRequest,
account_id: str,
) -> UnifiedMessage | None:
ur = skill_request.user_request
bot_user_key = ur.user.bot_user_key
utterance = ur.utterance
if not bot_user_key and not utterance:
return None
flow_type = skill_request.flow.trigger.type
is_button_click = flow_type in (
"CARD_BUTTON_MESSAGE", "CARD_BUTTON_BLOCK",
"LIST_ITEM_MESSAGE", "LIST_ITEM_BLOCK",
"LISTMENU_MESSAGE", "LISTMENU_BLOCK",
"QUICKREPLY_BUTTON_MESSAGE", "QUICKREPLY_BUTTON_BLOCK",
)
message_type = MessageType.EVENT if is_button_click else MessageType.TEXT
content = utterance
if is_button_click and not content:
content = f"[{flow_type}]"
sender = PeerInfo(
kind=PeerKind.DIRECT,
id=bot_user_key or ur.user.id,
display_name=None,
is_bot=False,
is_self=False,
)
msg_id = self._build_msg_id(account_id, skill_request)
metadata: dict = {
"bot_user_key": bot_user_key,
"is_friend": ur.user.is_friend,
"lang": ur.lang,
"intent_name": skill_request.intent.name,
"block_id": ur.block_id,
"is_direct": True,
"flow_trigger_type": flow_type,
"is_button_click": is_button_click,
"app_user_id": ur.user.app_user_id,
}
knowledge_extra = skill_request.intent.extra.get("knowledge", {})
matched = knowledge_extra.get("matchedKnowledges", [])
if matched:
knowledge_texts = [
item.get("answer", "")[:200]
for item in matched
if item.get("answer")
]
metadata["knowledge_matched"] = knowledge_texts
metadata["knowledge_count"] = len(matched)
return UnifiedMessage(
msg_id=msg_id,
channel_type="kakaotalk",
account_id=account_id,
content=content,
sender=sender,
message_type=message_type,
media_urls=[],
group=None,
timestamp=datetime.now(timezone.utc),
raw_payload=skill_request,
reply_to_id=None,
metadata=metadata,
)
@staticmethod
def _build_msg_id(account_id: str, skill_request: SkillRequest) -> str:
ur = skill_request.user_request
raw = f"{account_id}|{ur.utterance}|{ur.user.bot_user_key}|{ur.block_id}"
hash_val = hashlib.sha256(raw.encode()).hexdigest()[:16]
return f"kakaotalk:{hash_val}"