Files
banban/talkingq-url/banban/service/pending_voice_message.py
2026-05-27 16:31:44 +08:00

241 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from dataclasses import dataclass
from banban.dao.im import ImDAO
from banban.dao.pending_voice_message import PendingVoiceMessageDAO
from banban.service.device_audio_cache import device_audio_cache_service
from config import settings
from services.database_service_base import DatabaseServiceBase
from utils.logger import session_logger
@dataclass(frozen=True)
class PendingVoicePlaybackItem:
pending_id: int
audio_url: str
im_message_id: int
media_file_key: str
source: str
@dataclass(frozen=True)
class LatestVoicePlaybackItem:
audio_url: str
im_message_id: int
media_file_key: str
source: str = "latest_history_fallback"
class PendingVoiceMessageService(DatabaseServiceBase):
def __init__(self) -> None:
super().__init__(service_name="pending_voice_message")
def get_pending_notice_audio_url(self) -> str:
return f"http://{settings.server_host}:{settings.server_port}/assets/audio/new_message.mp3"
async def notify_device_pending_message(self, *, target_device_id: str) -> bool:
normalized_target_device_id = (target_device_id or "").strip()
if not normalized_target_device_id:
return False
try:
from handlers.mqtt_handler import TalkingQMQTTService
service = await TalkingQMQTTService.get_instance()
if service is None:
session_logger.warning(
normalized_target_device_id,
"pending_voice",
"MQTT服务未初始化跳过待收听留言提醒",
)
return False
audio_url = self.get_pending_notice_audio_url()
await service.send_nfc_notice(normalized_target_device_id, audio_url)
session_logger.info(
normalized_target_device_id,
"pending_voice",
f"待收听留言提醒已发送: audio_url={audio_url}",
)
return True
except Exception as exc:
session_logger.error(
normalized_target_device_id,
"pending_voice",
f"待收听留言提醒发送失败: {exc}",
exc_info=True,
)
return False
async def add_pending_message(
self,
*,
target_device_id: str,
im_message_id: int,
media_file_key: str,
sender_device_id: str | None = None,
audio_url: str | None = None,
source: str = "unknown",
) -> None:
normalized_target_device_id = (target_device_id or "").strip()
normalized_media_file_key = (media_file_key or "").strip()
if not normalized_target_device_id or not normalized_media_file_key:
raise ValueError("target_device_id and media_file_key are required")
db_session = await self.get_session()
try:
dao = PendingVoiceMessageDAO(db_session)
await dao.upsert_pending(
target_device_id=normalized_target_device_id,
sender_device_id=(sender_device_id or "").strip() or None,
im_message_id=int(im_message_id),
media_file_key=normalized_media_file_key,
audio_url=(audio_url or "").strip() or None,
source=source,
)
await db_session.commit()
session_logger.info(
normalized_target_device_id,
"pending_voice",
(
"待收听留言已持久化: "
f"im_message_id={im_message_id}, source={source}"
),
)
except Exception:
await db_session.rollback()
raise
finally:
await db_session.close()
await self.notify_device_pending_message(
target_device_id=normalized_target_device_id,
)
async def get_playback_items(
self,
*,
target_device_id: str,
limit: int = 10,
) -> list[PendingVoicePlaybackItem]:
db_session = await self.get_session()
try:
dao = PendingVoiceMessageDAO(db_session)
rows = await dao.list_pending_for_delivery(
target_device_id=target_device_id,
limit=limit,
)
finally:
await db_session.close()
items: list[PendingVoicePlaybackItem] = []
for row in rows:
media_file_key = str(row["media_file_key"])
fallback_audio_url = (row["audio_url"] or "").strip()
try:
audio_url = await device_audio_cache_service.get_device_audio_url(
media_file_key,
device_id=target_device_id,
)
except Exception as exc:
if fallback_audio_url:
audio_url = fallback_audio_url
else:
session_logger.error(
target_device_id,
"pending_voice",
f"待收听留言音频URL生成失败: media_file_key={media_file_key}, error={exc}",
exc_info=True,
)
continue
items.append(
PendingVoicePlaybackItem(
pending_id=int(row["id"]),
audio_url=audio_url,
im_message_id=int(row["im_message_id"]),
media_file_key=media_file_key,
source=str(row["source"] or "unknown"),
)
)
return items
async def get_latest_history_playback_item(
self,
*,
target_device_id: str,
) -> LatestVoicePlaybackItem | None:
db_session = await self.get_session()
try:
dao = ImDAO(db_session)
row = await dao.get_latest_voice_message_received_by_device(
device_id=target_device_id,
)
finally:
await db_session.close()
if not row:
return None
media_file_key = str(row["media_file_key"] or "").strip()
if not media_file_key:
return None
try:
audio_url = await device_audio_cache_service.get_device_audio_url(
media_file_key,
device_id=target_device_id,
)
except Exception as exc:
session_logger.error(
target_device_id,
"pending_voice",
(
"最近历史留言音频URL生成失败: "
f"message_id={row['id']}, media_file_key={media_file_key}, error={exc}"
),
exc_info=True,
)
return None
return LatestVoicePlaybackItem(
audio_url=audio_url,
im_message_id=int(row["id"]),
media_file_key=media_file_key,
)
async def mark_delivered(self, pending_ids: list[int]) -> None:
if not pending_ids:
return
db_session = await self.get_session()
try:
dao = PendingVoiceMessageDAO(db_session)
await dao.mark_delivered(pending_ids)
await db_session.commit()
except Exception:
await db_session.rollback()
raise
finally:
await db_session.close()
async def clear_pending_for_device(self, *, target_device_id: str) -> None:
db_session = await self.get_session()
try:
dao = PendingVoiceMessageDAO(db_session)
await dao.clear_pending_for_device(target_device_id=target_device_id)
await db_session.commit()
except Exception:
await db_session.rollback()
raise
finally:
await db_session.close()
async def list_devices_with_pending(self, *, limit: int = 1000) -> list[str]:
db_session = await self.get_session()
try:
dao = PendingVoiceMessageDAO(db_session)
rows = await dao.list_devices_with_pending(limit=limit)
finally:
await db_session.close()
return [str(row["target_device_id"]) for row in rows]
pending_voice_message_service = PendingVoiceMessageService()