新增 KakaoTalk 渠道扩展,支持在 Yuxi 平台中集成 KakaoTalk 即时通讯渠道。 包含以下功能模块: - bot: Bot 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - card_builder: KakaoTalk 卡片消息构建 - quick_reply: 快捷回复处理 - types: 类型定义
110 lines
3.6 KiB
Python
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}" |