"""Row/response serialization helpers for the kommunikation plugin.""" from __future__ import annotations import logging import re from typing import Any from app.plugins.builtins.kommunikation.models import ( CommConversation, CommMessage, CommMessageAttachment, CommMessageBlock, CommMessageReaction, CommParticipant, ) logger = logging.getLogger(__name__) MAX_TRIGGER_DEPTH = 3 # ─── serializers ─── # ─── Mention Parsing ─── MENTION_PATTERN = re.compile(r"@(\w+)") def parse_mentions(content: str) -> list[str]: """Parse @mentions from message content. Returns list of mentioned types/names.""" return MENTION_PATTERN.findall(content) # ─── Conversation Helpers ─── def conversation_to_response( conv: CommConversation, participants: list[CommParticipant], unread_count: int = 0, is_pinned_by_user: bool = False, ) -> dict[str, Any]: """Convert a CommConversation to a response dict.""" return { "id": str(conv.id), "title": conv.title, "is_locked": conv.is_locked, "locked_by": conv.locked_by, "is_direct": conv.is_direct, "is_archived": conv.is_archived, "is_pinned": is_pinned_by_user, "created_by": str(conv.created_by) if conv.created_by else None, "created_by_type": conv.created_by_type, "last_msg_at": conv.last_msg_at.isoformat() if conv.last_msg_at else None, "last_msg_preview": conv.last_msg_preview, "last_msg_sender_type": conv.last_msg_sender_type, "participants": [participant_to_response(p) for p in participants], "unread_count": unread_count, "metadata": conv.metadata_ or {}, } def participant_to_response(p: CommParticipant) -> dict[str, Any]: """Convert a CommParticipant to a response dict.""" return { "id": str(p.id), "conversation_id": str(p.conversation_id), "participant_id": str(p.participant_id) if p.participant_id else None, "participant_type": p.participant_type, "display_name": p.display_name, "role": p.role, "joined_at": p.joined_at.isoformat() if p.joined_at else None, } def message_to_response( msg: CommMessage, blocks: list[CommMessageBlock] | None = None, attachments: list[CommMessageAttachment] | None = None, reactions: list[CommMessageReaction] | None = None, ) -> dict[str, Any]: """Convert a CommMessage to a response dict.""" return { "id": str(msg.id), "conversation_id": str(msg.conversation_id), "sender_id": str(msg.sender_id) if msg.sender_id else None, "sender_type": msg.sender_type, "content": msg.content, "content_format": msg.content_format, "metadata": msg.metadata_ or {}, "reply_to_id": str(msg.reply_to_id) if msg.reply_to_id else None, "is_pinned": msg.is_pinned, "created_at": msg.created_at.isoformat() if msg.created_at else None, "edited_at": msg.edited_at.isoformat() if msg.edited_at else None, "blocks": [ { "id": str(b.id), "block_type": b.block_type, "block_data": b.block_data, "sort_order": b.sort_order, } for b in (blocks or []) ], "attachments": [ { "id": str(a.id), "file_id": str(a.file_id) if a.file_id else None, "file_source": a.file_source, "file_name": a.file_name, "file_type": a.file_type, "file_size": a.file_size, "thumbnail_path": a.thumbnail_path, } for a in (attachments or []) ], "reactions": [ { "id": str(r.id), "message_id": str(r.message_id), "user_id": str(r.user_id), "emoji": r.emoji, } for r in (reactions or []) ], } # ─── Conversation CRUD ───