from __future__ import annotations from typing import TYPE_CHECKING, Any from yuxi.channels.models import DeliveryResult from yuxi.utils.logging_config import logger if TYPE_CHECKING: from nio import AsyncClient _MSC3245_VOICE_FLAG = "org.matrix.msc3245.voice" _MSC1767_AUDIO_FLAG = "org.matrix.msc1767.audio" async def send_voice_note( client: AsyncClient, room_id: str, audio_data: bytes, filename: str = "voice.ogg", duration_ms: int = 0, mime_type: str = "audio/ogg", caption: str = "", reply_to: str | None = None, ) -> DeliveryResult: try: upload_resp, _ = await client.upload(audio_data, content_type=mime_type, filename=filename) except Exception as e: return DeliveryResult(success=False, error=f"Voice upload failed: {e}") mxc_url = upload_resp.content_uri logger.debug(f"Matrix voice note uploaded: {mxc_url}") content: dict[str, Any] = { "msgtype": "m.audio", "body": caption or filename, "url": mxc_url, "info": {"mimetype": mime_type, "duration": duration_ms}, } content[_MSC3245_VOICE_FLAG] = {} content[_MSC1767_AUDIO_FLAG] = {"duration": duration_ms} if reply_to: content["m.relates_to"] = {"m.in_reply_to": {"event_id": reply_to}} try: resp = await client.room_send( room_id=room_id, message_type="m.room.message", content=content, ) return DeliveryResult(success=True, message_id=resp.event_id) except Exception as e: return DeliveryResult(success=False, error=str(e)) async def send_tts_message( client: AsyncClient, room_id: str, text: str, audio_data: bytes, filename: str = "tts.ogg", duration_ms: int = 0, mime_type: str = "audio/ogg", ) -> DeliveryResult: tts_caption = f"[TTS] {text[:200]}" + ("..." if len(text) > 200 else "") return await send_voice_note( client=client, room_id=room_id, audio_data=audio_data, filename=filename, duration_ms=duration_ms, mime_type=mime_type, caption=tts_caption, ) def is_voice_message(event_source: dict[str, Any]) -> bool: content = event_source.get("content", {}) if content.get("msgtype") == "m.audio": if _MSC3245_VOICE_FLAG in content: return True if _MSC1767_AUDIO_FLAG in content: audio_data = content[_MSC1767_AUDIO_FLAG] if isinstance(audio_data, dict) and audio_data.get("duration"): return True return False def extract_audio_info(event_source: dict[str, Any]) -> dict[str, Any]: content = event_source.get("content", {}) info = content.get("info", {}) return { "duration_ms": info.get("duration", 0), "mime_type": info.get("mimetype", ""), "size": info.get("size", 0), "is_voice": is_voice_message(event_source), }