ForcePilot/backend/package/yuxi/channels/adapters/matrix/voice.py
Kris 2063145ce1 feat(matrix): 新增完整的Matrix适配器工具集
新增了20+个Matrix相关工具模块,包含线程处理、自动加入、消息去重、配置管理、安全策略、媒体处理、命令解析等功能,完善Matrix适配器基础能力
2026-05-12 00:45:54 +08:00

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),
}