新增 Zalo OA 官方账号完整集成能力,包含: 1. 基础通信能力:消息编解码、目标归一化、文本分块 2. 安全与校验:Webhook 签名验证、DM 策略管理、配对流程 3. 辅助工具:重复事件去重、请求限流、异常告警 4. 管理功能:账号多实例管理、配置验证、健康诊断 5. 扩展能力:媒体托管、视觉识别、TTS 语音合成 6. 运维支持:审计日志、状态监控、目录同步
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from yuxi.channels.models import DeliveryResult
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
DEFAULT_TTS_MODEL = "tts-1"
|
|
DEFAULT_TTS_VOICE = "alloy"
|
|
SYNTHESIS_TARGET = "voice-note"
|
|
|
|
|
|
class ZaloOAVoice:
|
|
"""Zalo OA TTS/Voice 集成 — 文本转语音并发送音频消息.
|
|
|
|
synthesis_target: voice-note — TTS 输出以音频文件形式作为消息附件发送.
|
|
"""
|
|
|
|
synthesis_target: str = SYNTHESIS_TARGET
|
|
|
|
def __init__(self, config: dict[str, Any] | None = None):
|
|
self._config = config or {}
|
|
self._enabled = self._config.get("voice_tts_enabled", False)
|
|
self._model = self._config.get("voice_tts_model", DEFAULT_TTS_MODEL)
|
|
self._voice = self._config.get("voice_tts_voice", DEFAULT_TTS_VOICE)
|
|
self._api_url = self._config.get("voice_tts_api_url", "")
|
|
self._api_key = self._config.get("voice_tts_api_key", "")
|
|
self._timeout = self._config.get("voice_tts_timeout_sec", 30)
|
|
|
|
@property
|
|
def enabled(self) -> bool:
|
|
return self._enabled and bool(self._api_url)
|
|
|
|
async def synthesize_and_send(
|
|
self,
|
|
text: str,
|
|
recipient_id: str,
|
|
sender: Any,
|
|
) -> DeliveryResult:
|
|
if not self._enabled or not self._api_url:
|
|
return DeliveryResult(success=False, error="TTS not configured")
|
|
|
|
try:
|
|
audio_data = await self._synthesize(text)
|
|
if not audio_data:
|
|
return DeliveryResult(success=False, error="TTS synthesis produced no audio")
|
|
|
|
return await sender.send_audio(recipient_id, audio_data, filename="voice.mp3")
|
|
except Exception as e:
|
|
logger.warning(f"[ZaloOA] TTS failed: {e}")
|
|
return DeliveryResult(success=False, error=str(e))
|
|
|
|
async def _synthesize(self, text: str) -> bytes | None:
|
|
import aiohttp
|
|
|
|
payload = {
|
|
"model": self._model,
|
|
"input": text,
|
|
"voice": self._voice,
|
|
"response_format": "mp3",
|
|
}
|
|
headers = {"Authorization": f"Bearer {self._api_key}"} if self._api_key else {}
|
|
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
self._api_url,
|
|
json=payload,
|
|
headers=headers,
|
|
timeout=aiohttp.ClientTimeout(total=self._timeout),
|
|
) as resp:
|
|
if resp.status == 200:
|
|
return await resp.read()
|
|
logger.warning(f"[ZaloOA] TTS API returned status {resp.status}")
|
|
return None
|
|
except Exception as e:
|
|
logger.warning(f"[ZaloOA] TTS API call failed: {e}")
|
|
return None
|