from __future__ import annotations import json from typing import Any from yuxi.channels.models import ( Attachment, ChannelIdentity, ChannelMessage, ChannelType, ChatType, EventType, MentionsInfo, MessageType, ) from .session import generate_dingding_chat_id, resolve_dingding_chat_type def map_event_type(raw: dict) -> EventType: if raw.get("event_type") == "card_action": return EventType.CARD_ACTION if raw.get("action_from") == "card_callback": return EventType.CARD_ACTION if raw.get("robotCode"): return EventType.BOT_ADDED return EventType.MESSAGE_RECEIVED def map_msg_type(raw: dict) -> MessageType: msg_type = raw.get("msgtype", "text") _map: dict[str, MessageType] = { "text": MessageType.TEXT, "image": MessageType.IMAGE, "file": MessageType.FILE, "voice": MessageType.AUDIO, "video": MessageType.VIDEO, "markdown": MessageType.TEXT, "link": MessageType.TEXT, "actionCard": MessageType.CARD, "feedCard": MessageType.CARD, "oa": MessageType.TEXT, "sticker": MessageType.STICKER, } return _map.get(msg_type, MessageType.TEXT) def _parse_content(content: Any) -> dict: if isinstance(content, dict): return content if isinstance(content, str): try: return json.loads(content) except (json.JSONDecodeError, TypeError): return {} return {} def _describe_media(msg_type: str, parsed: dict, raw: dict | None = None) -> str: if msg_type == "image": return "[图片]" if msg_type == "file": filename = parsed.get("fileName", parsed.get("file_name", "")) if not filename and raw: filename = raw.get("fileName", "") return f"[文件: {filename}]" if filename else "[文件]" if msg_type == "voice": return "[语音]" if msg_type == "video": return "[视频]" if msg_type == "sticker": return "[贴纸]" return "" def extract_text(raw: dict) -> str: msg_type = raw.get("msgtype", "text") text_block = raw.get("text", {}) if msg_type in ("image", "file", "voice", "video", "sticker"): parsed = _parse_content(raw.get("content", text_block)) return _describe_media(msg_type, parsed, raw) if isinstance(text_block, dict): return text_block.get("content", "") if isinstance(text_block, str): return text_block try: return str(text_block) if text_block else "" except Exception: return "" def extract_attachments(raw: dict) -> list[Attachment]: msg_type = raw.get("msgtype", "text") content = raw.get("content", {}) if msg_type == "image": download_code = raw.get("downloadCode", "") or raw.get("download_code", "") if download_code: return [Attachment(type="image", file_id=download_code)] return [] if msg_type == "file": download_code = raw.get("downloadCode", "") or raw.get("download_code", "") if download_code: return [ Attachment( type="file", file_id=download_code, filename=raw.get("fileName", ""), ) ] return [] if msg_type == "voice": download_code = raw.get("downloadCode", "") or raw.get("download_code", "") if download_code: return [Attachment(type="audio", file_id=download_code)] return [] if msg_type == "video": download_code = raw.get("downloadCode", "") or raw.get("download_code", "") if download_code: return [Attachment(type="video", file_id=download_code)] return [] if msg_type == "sticker": parsed = _parse_content(content) download_code = parsed.get("downloadCode", "") or parsed.get("download_code", "") if download_code: return [Attachment(type="sticker", file_id=download_code)] return [] return [] def extract_mentions(raw: dict) -> MentionsInfo | None: if not raw.get("isGroupChat"): return None mentioned_ids: list[str] = [] is_at_all = raw.get("isAtAll", False) at_users = raw.get("atUsers", []) if isinstance(at_users, list): for u in at_users: if isinstance(u, dict): uid = u.get("dingtalkId", "") if uid: mentioned_ids.append(uid) if is_at_all: mentioned_ids.append("@all") raw_text = extract_text(raw).strip() return MentionsInfo( mentioned_user_ids=mentioned_ids, is_bot_mentioned=bool(mentioned_ids), raw_text=raw_text, ) def normalize_inbound( channel_id: str, channel_type: ChannelType, raw_payload: dict[str, Any], ) -> ChannelMessage: chat_type_str = resolve_dingding_chat_type(raw_payload) chat_type = ChatType(chat_type_str) chat_id = generate_dingding_chat_id(raw_payload, chat_type_str) sender_id = ( raw_payload.get("senderId", "") or raw_payload.get("senderStaffId", "") or raw_payload.get("senderNick", "") or "unknown" ) message_id = raw_payload.get("msgId", "") or raw_payload.get("openMsgId", "") event_type = map_event_type(raw_payload) msg_type = map_msg_type(raw_payload) content = extract_text(raw_payload) attachments = extract_attachments(raw_payload) mentions = extract_mentions(raw_payload) metadata: dict[str, Any] = { "msgtype": raw_payload.get("msgtype", "text"), "isGroupChat": raw_payload.get("isGroupChat", False), } if raw_payload.get("rootId"): metadata["root_id"] = raw_payload["rootId"] if raw_payload.get("conversationId"): metadata["conversation_id"] = raw_payload["conversationId"] return ChannelMessage( identity=ChannelIdentity( channel_id=channel_id, channel_type=channel_type, channel_user_id=sender_id, channel_chat_id=chat_id, channel_message_id=message_id, ), event_type=event_type, message_type=msg_type, chat_type=chat_type, content=content, attachments=attachments, mentions=mentions, metadata=metadata, )