实现了 Teams 机器人所需的全功能组件,包括: - 基础命令解析与帮助卡片生成 - 租户验证与访问控制 - 自定义 UA 与媒体工具 - 消息分块、批注处理与会话管理 - 防抖、缓存与配置路由能力 - 投票、配对、审计与运行时状态管理 - TTS 语音合成与卡片构建工具 - 群组管理与权限控制逻辑
101 lines
2.6 KiB
Python
101 lines
2.6 KiB
Python
"""Microsoft Teams TTS/Voice 能力。
|
|
|
|
Voice 附件出站发送,音频处理。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
def build_voice_activity(
|
|
audio_data: bytes,
|
|
mime_type: str = "audio/wav",
|
|
filename: str = "voice.wav",
|
|
reply_to_id: str | None = None,
|
|
) -> dict[str, Any]:
|
|
audio_b64 = base64.b64encode(audio_data).decode()
|
|
content_url = f"data:{mime_type};base64,{audio_b64}"
|
|
|
|
activity: dict[str, Any] = {
|
|
"type": "message",
|
|
"attachments": [
|
|
{
|
|
"contentType": mime_type,
|
|
"contentUrl": content_url,
|
|
"name": filename,
|
|
}
|
|
],
|
|
}
|
|
if reply_to_id:
|
|
activity["replyToId"] = reply_to_id
|
|
return activity
|
|
|
|
|
|
def build_tts_activity(
|
|
text: str,
|
|
voice: str = "zh-CN-XiaoxiaoNeural",
|
|
reply_to_id: str | None = None,
|
|
) -> dict[str, Any]:
|
|
activity: dict[str, Any] = {
|
|
"type": "message",
|
|
"text": text,
|
|
"textFormat": "markdown",
|
|
"channelData": {
|
|
"speak": text,
|
|
"voice": voice,
|
|
},
|
|
}
|
|
if reply_to_id:
|
|
activity["replyToId"] = reply_to_id
|
|
return activity
|
|
|
|
|
|
def parse_voice_attachment(
|
|
attachment: dict[str, Any],
|
|
) -> dict[str, Any] | None:
|
|
content_type = attachment.get("contentType", "")
|
|
if not content_type.startswith("audio/"):
|
|
return None
|
|
return {
|
|
"type": "audio",
|
|
"url": attachment.get("contentUrl", ""),
|
|
"name": attachment.get("name", "audio"),
|
|
"content_type": content_type,
|
|
}
|
|
|
|
|
|
async def synthesize_speech(
|
|
text: str,
|
|
voice: str = "zh-CN-XiaoxiaoNeural",
|
|
subscription_key: str = "",
|
|
region: str = "eastasia",
|
|
) -> bytes | None:
|
|
import aiohttp
|
|
|
|
endpoint = f"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1"
|
|
ssml = (
|
|
f'<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="zh-CN">'
|
|
f'<voice name="{voice}">{text}</voice>'
|
|
f"</speak>"
|
|
)
|
|
|
|
headers = {
|
|
"Ocp-Apim-Subscription-Key": subscription_key,
|
|
"Content-Type": "application/ssml+xml",
|
|
"X-Microsoft-OutputFormat": "audio-16khz-128kbitrate-mono-mp3",
|
|
}
|
|
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(endpoint, headers=headers, data=ssml.encode()) as resp:
|
|
if resp.status == 200:
|
|
return await resp.read()
|
|
logger.warning(f"TTS synthesis failed: HTTP {resp.status}")
|
|
except Exception as e:
|
|
logger.error(f"TTS synthesis error: {e}")
|
|
return None
|