99 lines
2.9 KiB
Python
99 lines
2.9 KiB
Python
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),
|
|
}
|