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}"