Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox_Bot into dev
This commit is contained in:
15
README.md
15
README.md
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
# 🌟 MoFox_Bot
|
||||
**🚀 基于 MaiCore 的增强型 AI 智能体,功能更强大,体验更流畅**
|
||||
**🚀 基于 MaiCore 0.10.0 snapshot.5进一步开发的 AI 智能体,插件功能更强大**
|
||||
|
||||
</div>
|
||||
|
||||
@@ -36,12 +36,13 @@
|
||||
|
||||
## 📖 项目简介
|
||||
|
||||
**MoFox_Bot** 是一个基于 [MaiCore](https://github.com/MaiM-with-u/MaiBot) `0.10.0 snapshot.5` 的增强型 fork 项目。我们保留了原项目几乎所有核心功能,并在此基础上进行了深度优化与功能扩展,致力于打造一个**更稳定、更智能、更具趣味性**的 AI 智能体。
|
||||
**MoFox_Bot** 是一个基于 [MaiCore](https://github.com/MaiM-with-u/MaiBot) `0.10.0 snapshot.5` 的 fork 项目。我们保留了原项目几乎所有核心功能,并在此基础上进行了深度优化与功能扩展,致力于打造一个**更稳定、更智能、更具趣味性**的 AI 智能体。
|
||||
|
||||
|
||||
> [IMPORTANT]
|
||||
> **第三方项目声明**
|
||||
>
|
||||
> 本项目由 **MoFox Studio** 独立维护,为 **MaiBot 的第三方分支**,并非官方版本。所有更新与支持均由我们团队负责,与 MaiBot 官方无直接关系。
|
||||
> 本项目Fork后由 **MoFox Studio** 独立维护,为 **MaiBot 的第三方分支**,并非官方版本。所有更新与支持均由我们团队负责,后续的更新与 MaiBot 官方无直接关系。
|
||||
|
||||
> [WARNING]
|
||||
> **迁移风险提示**
|
||||
@@ -62,7 +63,7 @@
|
||||
<tr>
|
||||
<td width="50%">
|
||||
|
||||
### 🔧 原版功能(全部保留)
|
||||
### 🔧 MaiBot 0.10.0 snapshot.5 原版功能
|
||||
- 🔌 **强大插件系统** - 全面重构的插件架构,支持完整的管理 API 和权限控制
|
||||
- 💭 **实时思维系统** - 模拟人类思考过程
|
||||
- 📚 **表达学习功能** - 学习群友的说话风格和表达方式
|
||||
@@ -104,7 +105,7 @@
|
||||
| 🖥️ 操作系统 | Windows 10/11、macOS 10.14+、Linux (Ubuntu 18.04+) |
|
||||
| 🐍 Python 版本 | Python 3.11 或更高版本 |
|
||||
| 💾 内存 | 建议 ≥ 4GB 可用内存 |
|
||||
| 💿 存储空间 | 建议 ≥ 2GB 可用空间 |
|
||||
| 💿 存储空间 | 建议 ≥ 4GB 可用空间 |
|
||||
|
||||
### 🛠️ 依赖服务
|
||||
|
||||
@@ -150,10 +151,12 @@
|
||||
|
||||
| 项目 | 描述 | 贡献 |
|
||||
| ------------------------------------------ | -------------------- | ---------------- |
|
||||
| 🎯 [MaiM-with-u/MaiBot](https://github.com/MaiM-with-u/MaiBot) | 原版 MaiBot 框架 | 提供核心架构与设计 |
|
||||
| 🎯 [MaiM-with-u/MaiBot](https://github.com/Mai-with-u/MaiBot) | 原版 MaiBot 框架 | 提供核心架构与设计 |
|
||||
| 🐱 [NapNeko/NapCatQQ](https://github.com/NapNeko/NapCatQQ) | 高性能 QQ 协议端 | 实现稳定通信 |
|
||||
| 🌌 [internetsb/Maizone](https://github.com/internetsb/Maizone) | 魔改空间插件 | 功能借鉴与启发 |
|
||||
|
||||
如果可以的话,请为这些项目也点个 ⭐️ !(尤其是MaiBot)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
1
docs/OneKey-Plus
Normal file
1
docs/OneKey-Plus
Normal file
@@ -0,0 +1 @@
|
||||
准备放关于一键包的文档
|
||||
@@ -131,6 +131,7 @@ def init_prompt():
|
||||
{safety_guidelines_block}
|
||||
|
||||
{group_chat_reminder_block}
|
||||
- 在称呼用户时,请使用更自然的昵称或简称。对于长英文名,可使用首字母缩写;对于中文名,可提炼合适的简称。禁止直接复述复杂的用户名或输出用户名中的任何符号,让称呼更像人类习惯,注意,简称不是必须的,合理的使用。
|
||||
你的回复应该是一条简短、完整且口语化的回复。
|
||||
|
||||
--------------------------------
|
||||
@@ -213,6 +214,7 @@ If you need to use the search tool, please directly call the function "lpmm_sear
|
||||
## 规则
|
||||
{safety_guidelines_block}
|
||||
{group_chat_reminder_block}
|
||||
- 在称呼用户时,请使用更自然的昵称或简称。对于长英文名,可使用首字母缩写;对于中文名,可提炼合适的简称。禁止直接复述复杂的用户名或输出用户名中的任何符号,让称呼更像人类习惯,注意,简称不是必须的,合理的使用。
|
||||
你的回复应该是一条简短、完整且口语化的回复。
|
||||
|
||||
--------------------------------
|
||||
|
||||
@@ -70,7 +70,7 @@ class ModelInfo(ValidatedConfigBase):
|
||||
price_out: float = Field(default=0.0, ge=0, description="每M token输出价格")
|
||||
force_stream_mode: bool = Field(default=False, description="是否强制使用流式输出模式")
|
||||
extra_params: dict[str, Any] = Field(default_factory=dict, description="额外参数(用于API调用时的额外配置)")
|
||||
anti_truncation: bool = Field(default=False, description="是否启用反截断功能,防止模型输出被截断")
|
||||
anti_truncation: bool = Field(default=False, alias="use_anti_truncation", description="是否启用反截断功能,防止模型输出被截断")
|
||||
enable_prompt_perturbation: bool = Field(default=False, description="是否启用提示词扰动(合并了内容混淆和注意力优化)")
|
||||
perturbation_strength: Literal["light", "medium", "heavy"] = Field(
|
||||
default="light", description="扰动强度(light/medium/heavy)"
|
||||
|
||||
@@ -42,7 +42,6 @@ logger = get_logger("main")
|
||||
# 预定义彩蛋短语,避免在每次初始化时重新创建
|
||||
EGG_PHRASES: list[tuple[str, int]] = [
|
||||
("我们的代码里真的没有bug,只有'特性'。", 10),
|
||||
("你知道吗?阿范喜欢被切成臊子😡", 10),
|
||||
("你知道吗,雅诺狐的耳朵其实很好摸", 5),
|
||||
("你群最高技术力————言柒姐姐!", 20),
|
||||
("初墨小姐宇宙第一(不是)", 10),
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "SiliconFlow IndexTTS 语音合成插件",
|
||||
"version": "2.0.0",
|
||||
"description": "基于SiliconFlow API的IndexTTS语音合成插件,使用IndexTeam/IndexTTS-2模型支持高质量的零样本语音克隆。",
|
||||
"author": {
|
||||
"name": "MoFox Studio",
|
||||
"url": "https://github.com/MoFox-Studio"
|
||||
},
|
||||
"license": "GPL-v3.0-or-later",
|
||||
|
||||
"host_application": {
|
||||
"min_version": "0.8.0"
|
||||
},
|
||||
"homepage_url": "https://docs.siliconflow.cn/cn/userguide/capabilities/text-to-speech",
|
||||
"repository_url": "https://github.com/MoFox-Studio/MoFox-Bot",
|
||||
"keywords": ["tts", "voice", "audio", "speech", "indextts", "voice-cloning", "siliconflow"],
|
||||
"categories": ["Audio Tools", "Voice Assistant", "AI Tools"],
|
||||
|
||||
"default_locale": "zh-CN",
|
||||
"locales_path": "_locales",
|
||||
|
||||
"plugin_info": {
|
||||
"is_built_in": true,
|
||||
"plugin_type": "audio_processor",
|
||||
"components": [
|
||||
{
|
||||
"type": "action",
|
||||
"name": "siliconflow_indextts_action",
|
||||
"description": "使用SiliconFlow API进行IndexTTS语音合成",
|
||||
"activation_modes": ["llm_judge", "keyword"],
|
||||
"keywords": ["克隆语音", "模仿声音", "语音合成", "indextts", "声音克隆", "语音生成", "仿声", "变声"]
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"name": "siliconflow_tts_cmd",
|
||||
"description": "SiliconFlow IndexTTS语音合成命令",
|
||||
"command_name": "sf_tts",
|
||||
"aliases": ["sftts", "sf语音", "硅基语音"]
|
||||
}
|
||||
],
|
||||
"features": [
|
||||
"零样本语音克隆",
|
||||
"情感控制语音合成",
|
||||
"自定义参考音频",
|
||||
"高质量音频输出",
|
||||
"多种语音风格"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# 参考音频目录
|
||||
|
||||
将您的参考音频文件放置在此目录中,用于语音克隆功能。
|
||||
|
||||
## 音频要求
|
||||
|
||||
- **格式**: WAV, MP3, M4A
|
||||
- **采样率**: 16kHz 或 24kHz
|
||||
- **时长**: 3-30秒(推荐5-10秒)
|
||||
- **质量**: 语音清晰,无背景噪音
|
||||
- **内容**: 自然语音,避免音乐或特效
|
||||
|
||||
## 文件命名建议
|
||||
|
||||
- 使用描述性的文件名,例如:
|
||||
- `male_voice_calm.wav` - 男声平静
|
||||
- `female_voice_cheerful.wav` - 女声活泼
|
||||
- `child_voice_cute.wav` - 童声可爱
|
||||
- `elderly_voice_wise.wav` - 老年声音睿智
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 将音频文件复制到此目录
|
||||
2. 在命令中使用文件名:
|
||||
```
|
||||
/sf_tts "测试文本" --ref "your_audio.wav"
|
||||
```
|
||||
3. 或在配置中设置默认参考音频:
|
||||
```toml
|
||||
[synthesis]
|
||||
default_reference_audio = "your_audio.wav"
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保您有使用这些音频的合法权限
|
||||
- 音频质量会直接影响克隆效果
|
||||
- 建议定期清理不需要的音频文件
|
||||
|
||||
## 示例音频
|
||||
|
||||
您可以录制或收集一些不同风格的音频:
|
||||
|
||||
- **情感类型**: 开心、悲伤、愤怒、平静、激动
|
||||
- **说话风格**: 正式、随意、播报、对话
|
||||
- **音调特点**: 低沉、清亮、温柔、有力
|
||||
Binary file not shown.
Binary file not shown.
449
src/plugins/built_in/siliconflow_api_index_tts/plugin.py
Normal file
449
src/plugins/built_in/siliconflow_api_index_tts/plugin.py
Normal file
@@ -0,0 +1,449 @@
|
||||
"""
|
||||
SiliconFlow IndexTTS 语音合成插件
|
||||
基于SiliconFlow API的IndexTTS语音合成插件,支持高质量的零样本语音克隆和情感控制
|
||||
"""
|
||||
|
||||
import os
|
||||
import base64
|
||||
import hashlib
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import json
|
||||
import toml
|
||||
from typing import Tuple, Optional, Dict, Any, List, Type
|
||||
from pathlib import Path
|
||||
|
||||
from src.plugin_system import BasePlugin, BaseAction, BaseCommand, register_plugin, ConfigField
|
||||
from src.plugin_system.base.base_action import ActionActivationType, ChatMode
|
||||
from src.common.logger import get_logger
|
||||
|
||||
logger = get_logger("SiliconFlow-TTS")
|
||||
|
||||
|
||||
def get_global_siliconflow_api_key() -> Optional[str]:
|
||||
"""从全局配置文件中获取SiliconFlow API密钥"""
|
||||
try:
|
||||
# 读取全局model_config.toml配置文件
|
||||
config_path = Path("config/model_config.toml")
|
||||
if not config_path.exists():
|
||||
logger.error("全局配置文件 config/model_config.toml 不存在")
|
||||
return None
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
model_config = toml.load(f)
|
||||
|
||||
# 查找SiliconFlow API提供商配置
|
||||
api_providers = model_config.get("api_providers", [])
|
||||
for provider in api_providers:
|
||||
if provider.get("name") == "SiliconFlow":
|
||||
api_key = provider.get("api_key", "")
|
||||
if api_key:
|
||||
logger.info("成功从全局配置读取SiliconFlow API密钥")
|
||||
return api_key
|
||||
|
||||
logger.warning("在全局配置中未找到SiliconFlow API提供商或API密钥为空")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"读取全局配置失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
class SiliconFlowTTSClient:
|
||||
"""SiliconFlow TTS API客户端"""
|
||||
|
||||
def __init__(self, api_key: str, base_url: str = "https://api.siliconflow.cn/v1/audio/speech",
|
||||
timeout: int = 60, max_retries: int = 3):
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url
|
||||
self.timeout = timeout
|
||||
self.max_retries = max_retries
|
||||
|
||||
async def synthesize_speech(self, text: str, voice_id: str,
|
||||
model: str = "IndexTeam/IndexTTS-2",
|
||||
speed: float = 1.0, volume: float = 1.0,
|
||||
emotion_strength: float = 1.0,
|
||||
output_format: str = "wav") -> bytes:
|
||||
"""
|
||||
调用SiliconFlow API进行语音合成
|
||||
|
||||
Args:
|
||||
text: 要合成的文本
|
||||
voice_id: 预配置的语音ID
|
||||
model: 模型名称 (默认使用IndexTeam/IndexTTS-2)
|
||||
speed: 语速
|
||||
volume: 音量
|
||||
emotion_strength: 情感强度
|
||||
output_format: 输出格式
|
||||
|
||||
Returns:
|
||||
合成的音频数据
|
||||
"""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 构建请求数据
|
||||
data = {
|
||||
"model": model,
|
||||
"input": text,
|
||||
"voice": voice_id,
|
||||
"format": output_format,
|
||||
"speed": speed
|
||||
}
|
||||
|
||||
logger.info(f"使用配置的Voice ID: {voice_id}")
|
||||
|
||||
# 发送请求
|
||||
for attempt in range(self.max_retries):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
self.base_url,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
audio_data = await response.read()
|
||||
logger.info(f"语音合成成功,音频大小: {len(audio_data)} bytes")
|
||||
return audio_data
|
||||
else:
|
||||
error_text = await response.text()
|
||||
logger.error(f"API请求失败 (状态码: {response.status}): {error_text}")
|
||||
if attempt == self.max_retries - 1:
|
||||
raise Exception(f"API请求失败: {response.status} - {error_text}")
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(f"请求超时,尝试第 {attempt + 1}/{self.max_retries} 次")
|
||||
if attempt == self.max_retries - 1:
|
||||
raise Exception("请求超时")
|
||||
except Exception as e:
|
||||
logger.error(f"请求异常: {e}")
|
||||
if attempt == self.max_retries - 1:
|
||||
raise e
|
||||
await asyncio.sleep(2 ** attempt) # 指数退避
|
||||
|
||||
raise Exception("所有重试都失败了")
|
||||
|
||||
|
||||
class SiliconFlowIndexTTSAction(BaseAction):
|
||||
"""SiliconFlow IndexTTS Action组件"""
|
||||
|
||||
# 激活设置
|
||||
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||
normal_activation_type = ActionActivationType.KEYWORD
|
||||
mode_enable = ChatMode.ALL
|
||||
parallel_action = False
|
||||
|
||||
# 动作基本信息
|
||||
action_name = "siliconflow_indextts_action"
|
||||
action_description = "使用SiliconFlow API进行高质量的IndexTTS语音合成,支持零样本语音克隆"
|
||||
|
||||
# 关键词配置
|
||||
activation_keywords = ["克隆语音", "模仿声音", "语音合成", "indextts", "声音克隆", "语音生成", "仿声", "变声"]
|
||||
keyword_case_sensitive = False
|
||||
|
||||
# 动作参数定义
|
||||
action_parameters = {
|
||||
"text": "需要合成语音的文本内容,必填,应当清晰流畅",
|
||||
"speed": "语速(可选),范围0.1-3.0,默认1.0"
|
||||
}
|
||||
|
||||
# 动作使用场景
|
||||
action_require = [
|
||||
"当用户要求语音克隆或模仿某个声音时使用",
|
||||
"当用户明确要求进行语音合成时使用",
|
||||
"当需要高质量语音输出时使用",
|
||||
"当用户要求变声或仿声时使用"
|
||||
]
|
||||
|
||||
# 关联类型 - 支持语音消息
|
||||
associated_types = ["voice"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""执行SiliconFlow IndexTTS语音合成"""
|
||||
logger.info(f"{self.log_prefix} 执行SiliconFlow IndexTTS动作: {self.reasoning}")
|
||||
|
||||
# 优先从全局配置获取SiliconFlow API密钥
|
||||
api_key = get_global_siliconflow_api_key()
|
||||
if not api_key:
|
||||
# 如果全局配置中没有,则从插件配置获取(兼容旧版本)
|
||||
api_key = self.get_config("api.api_key", "")
|
||||
if not api_key:
|
||||
logger.error(f"{self.log_prefix} SiliconFlow API密钥未配置")
|
||||
return False, "请在全局配置 config/model_config.toml 中配置SiliconFlow API密钥"
|
||||
|
||||
# 获取文本内容 - 多种来源尝试
|
||||
text = ""
|
||||
|
||||
# 1. 尝试从action_data获取text参数
|
||||
text = self.action_data.get("text", "")
|
||||
if not text:
|
||||
# 2. 尝试从action_data获取tts_text参数(兼容其他TTS插件)
|
||||
text = self.action_data.get("tts_text", "")
|
||||
|
||||
if not text:
|
||||
# 3. 如果没有提供具体文本,则生成一个基于reasoning的语音回复
|
||||
if self.reasoning:
|
||||
# 基于内心思考生成适合语音播报的内容
|
||||
# 这里可以进行一些处理,让内心思考更适合作为语音输出
|
||||
if "阿范" in self.reasoning and any(word in self.reasoning for word in ["想听", "语音", "声音"]):
|
||||
# 如果reasoning表明用户想听语音,生成相应回复
|
||||
text = "喵~阿范想听我的声音吗?那就用这个新的语音合成功能试试看吧~"
|
||||
elif "测试" in self.reasoning:
|
||||
text = "好吧,那就试试这个新的语音合成功能吧~"
|
||||
else:
|
||||
# 使用reasoning的内容,但做适当调整
|
||||
text = self.reasoning
|
||||
logger.info(f"{self.log_prefix} 基于reasoning生成语音内容")
|
||||
else:
|
||||
# 如果完全没有内容,使用默认回复
|
||||
text = "喵~使用SiliconFlow IndexTTS测试语音合成功能~"
|
||||
logger.info(f"{self.log_prefix} 使用默认语音内容")
|
||||
|
||||
# 获取其他参数
|
||||
speed = float(self.action_data.get("speed", self.get_config("synthesis.speed", 1.0)))
|
||||
|
||||
try:
|
||||
# 获取预配置的voice_id
|
||||
voice_id = self.get_config("synthesis.voice_id", "")
|
||||
if not voice_id or not isinstance(voice_id, str):
|
||||
logger.error(f"{self.log_prefix} 配置中未找到有效的voice_id,请先运行upload_voice.py工具上传参考音频")
|
||||
return False, "配置中未找到有效的voice_id"
|
||||
|
||||
logger.info(f"{self.log_prefix} 使用预配置的voice_id: {voice_id}")
|
||||
|
||||
# 创建TTS客户端
|
||||
client = SiliconFlowTTSClient(
|
||||
api_key=api_key,
|
||||
base_url=self.get_config("api.base_url", "https://api.siliconflow.cn/v1/audio/speech"),
|
||||
timeout=self.get_config("api.timeout", 60),
|
||||
max_retries=self.get_config("api.max_retries", 3)
|
||||
)
|
||||
|
||||
# 合成语音
|
||||
audio_data = await client.synthesize_speech(
|
||||
text=text,
|
||||
voice_id=voice_id,
|
||||
model=self.get_config("synthesis.model", "IndexTeam/IndexTTS-2"),
|
||||
speed=speed,
|
||||
output_format=self.get_config("synthesis.output_format", "wav")
|
||||
)
|
||||
|
||||
# 转换为base64编码(语音消息需要base64格式)
|
||||
audio_base64 = base64.b64encode(audio_data).decode('utf-8')
|
||||
|
||||
# 发送语音消息(使用voice类型,支持WAV格式的base64)
|
||||
await self.send_custom(
|
||||
message_type="voice",
|
||||
content=audio_base64
|
||||
)
|
||||
|
||||
# 记录动作信息
|
||||
await self.store_action_info(
|
||||
action_build_into_prompt=True,
|
||||
action_prompt_display=f"已使用SiliconFlow IndexTTS生成语音: {text[:20]}...",
|
||||
action_done=True
|
||||
)
|
||||
|
||||
logger.info(f"{self.log_prefix} 语音合成成功,文本长度: {len(text)}")
|
||||
return True, "SiliconFlow IndexTTS语音合成成功"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 语音合成失败: {e}")
|
||||
return False, f"语音合成失败: {str(e)}"
|
||||
|
||||
|
||||
class SiliconFlowTTSCommand(BaseCommand):
|
||||
"""SiliconFlow TTS命令组件"""
|
||||
|
||||
command_name = "sf_tts"
|
||||
command_description = "使用SiliconFlow IndexTTS进行语音合成"
|
||||
command_aliases = ["sftts", "sf语音", "硅基语音"]
|
||||
|
||||
command_parameters = {
|
||||
"text": {"type": str, "required": True, "description": "要合成的文本"},
|
||||
"speed": {"type": float, "required": False, "description": "语速 (0.1-3.0)"}
|
||||
}
|
||||
|
||||
async def execute(self, text: str, speed: float = 1.0) -> Tuple[bool, str]:
|
||||
"""执行TTS命令"""
|
||||
logger.info(f"{self.log_prefix} 执行SiliconFlow TTS命令")
|
||||
|
||||
# 优先从全局配置获取SiliconFlow API密钥
|
||||
api_key = get_global_siliconflow_api_key()
|
||||
if not api_key:
|
||||
# 如果全局配置中没有,则从插件配置获取(兼容旧版本)
|
||||
plugin = self.get_plugin()
|
||||
api_key = plugin.get_config("api.api_key", "")
|
||||
if not api_key:
|
||||
await self.send_reply("❌ SiliconFlow API密钥未配置!请在全局配置 config/model_config.toml 中设置。")
|
||||
return False, "API密钥未配置"
|
||||
|
||||
try:
|
||||
await self.send_reply("正在使用SiliconFlow IndexTTS合成语音,请稍候...")
|
||||
|
||||
# 使用默认参考音频 refer.mp3
|
||||
# 通过插件文件所在目录获取audio_reference目录
|
||||
plugin_dir = Path(__file__).parent
|
||||
audio_dir = plugin_dir / "audio_reference"
|
||||
reference_audio_path = audio_dir / "refer.mp3"
|
||||
|
||||
if not reference_audio_path.exists():
|
||||
logger.warning(f"参考音频文件不存在: {reference_audio_path}")
|
||||
reference_audio_path = None
|
||||
|
||||
# 创建TTS客户端
|
||||
client = SiliconFlowTTSClient(
|
||||
api_key=api_key,
|
||||
base_url="https://api.siliconflow.cn/v1/audio/speech",
|
||||
timeout=60,
|
||||
max_retries=3
|
||||
)
|
||||
|
||||
# 合成语音
|
||||
audio_data = await client.synthesize_speech(
|
||||
text=text,
|
||||
reference_audio_path=str(reference_audio_path) if reference_audio_path else None,
|
||||
model="IndexTeam/IndexTTS-2",
|
||||
speed=speed,
|
||||
output_format="wav"
|
||||
)
|
||||
|
||||
# 生成临时文件名
|
||||
text_hash = hashlib.md5(text.encode()).hexdigest()[:8]
|
||||
filename = f"siliconflow_tts_{text_hash}.wav"
|
||||
|
||||
# 发送音频
|
||||
await self.send_custom(
|
||||
message_type="audio_file",
|
||||
content=audio_data,
|
||||
filename=filename
|
||||
)
|
||||
|
||||
await self.send_reply("✅ 语音合成完成!")
|
||||
return True, "命令执行成功"
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"❌ 语音合成失败: {str(e)}"
|
||||
await self.send_reply(error_msg)
|
||||
logger.error(f"{self.log_prefix} 命令执行失败: {e}")
|
||||
return False, str(e)
|
||||
|
||||
|
||||
@register_plugin
|
||||
class SiliconFlowIndexTTSPlugin(BasePlugin):
|
||||
"""SiliconFlow IndexTTS插件主类"""
|
||||
|
||||
plugin_name = "siliconflow_api_index_tts"
|
||||
plugin_description = "基于SiliconFlow API的IndexTTS语音合成插件"
|
||||
plugin_version = "2.0.0"
|
||||
plugin_author = "MoFox Studio"
|
||||
|
||||
# 必需的抽象属性
|
||||
enable_plugin: bool = True
|
||||
dependencies: list[str] = []
|
||||
config_file_name: str = "config.toml"
|
||||
|
||||
# Python依赖
|
||||
python_dependencies = ["aiohttp>=3.8.0"]
|
||||
|
||||
# 配置描述
|
||||
config_section_descriptions = {
|
||||
"plugin": "插件基本配置",
|
||||
"components": "组件启用配置",
|
||||
"api": "SiliconFlow API配置",
|
||||
"synthesis": "语音合成配置"
|
||||
}
|
||||
|
||||
# 配置schema
|
||||
config_schema = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="2.0.0", description="配置文件版本"),
|
||||
},
|
||||
"components": {
|
||||
"enable_action": ConfigField(type=bool, default=True, description="是否启用Action组件"),
|
||||
"enable_command": ConfigField(type=bool, default=True, description="是否启用Command组件"),
|
||||
},
|
||||
"api": {
|
||||
"api_key": ConfigField(type=str, default="",
|
||||
description="SiliconFlow API密钥(可选,优先使用全局配置)"),
|
||||
"base_url": ConfigField(type=str, default="https://api.siliconflow.cn/v1/audio/speech",
|
||||
description="SiliconFlow TTS API地址"),
|
||||
"timeout": ConfigField(type=int, default=60, description="API请求超时时间(秒)"),
|
||||
"max_retries": ConfigField(type=int, default=3, description="API请求最大重试次数"),
|
||||
},
|
||||
"synthesis": {
|
||||
"model": ConfigField(type=str, default="IndexTeam/IndexTTS-2",
|
||||
description="TTS模型名称"),
|
||||
"speed": ConfigField(type=float, default=1.0,
|
||||
description="默认语速 (0.1-3.0)"),
|
||||
"output_format": ConfigField(type=str, default="wav",
|
||||
description="输出音频格式"),
|
||||
}
|
||||
}
|
||||
|
||||
def get_plugin_components(self):
|
||||
"""获取插件组件"""
|
||||
from src.plugin_system.base.component_types import ActionInfo, CommandInfo, ComponentType
|
||||
|
||||
components = []
|
||||
|
||||
# 检查配置是否启用组件
|
||||
if self.get_config("components.enable_action", True):
|
||||
action_info = ActionInfo(
|
||||
name="siliconflow_indextts_action",
|
||||
component_type=ComponentType.ACTION,
|
||||
description="使用SiliconFlow API进行高质量的IndexTTS语音合成",
|
||||
activation_keywords=["克隆语音", "模仿声音", "语音合成", "indextts", "声音克隆", "语音生成", "仿声", "变声"],
|
||||
plugin_name=self.plugin_name
|
||||
)
|
||||
components.append((action_info, SiliconFlowIndexTTSAction))
|
||||
|
||||
if self.get_config("components.enable_command", True):
|
||||
command_info = CommandInfo(
|
||||
name="sf_tts",
|
||||
component_type=ComponentType.COMMAND,
|
||||
description="使用SiliconFlow IndexTTS进行语音合成",
|
||||
plugin_name=self.plugin_name
|
||||
)
|
||||
components.append((command_info, SiliconFlowTTSCommand))
|
||||
|
||||
return components
|
||||
|
||||
async def on_plugin_load(self):
|
||||
"""插件加载时的回调"""
|
||||
logger.info("SiliconFlow IndexTTS插件已加载")
|
||||
|
||||
# 检查audio_reference目录
|
||||
audio_dir = Path(self.plugin_path) / "audio_reference"
|
||||
if not audio_dir.exists():
|
||||
audio_dir.mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"创建音频参考目录: {audio_dir}")
|
||||
|
||||
# 检查参考音频文件
|
||||
refer_file = audio_dir / "refer.mp3"
|
||||
if not refer_file.exists():
|
||||
logger.warning(f"参考音频文件不存在: {refer_file}")
|
||||
logger.info("请确保将自定义参考音频文件命名为 refer.mp3 并放置在 audio_reference 目录中")
|
||||
|
||||
# 检查API密钥配置(优先检查全局配置)
|
||||
api_key = get_global_siliconflow_api_key()
|
||||
if not api_key:
|
||||
# 检查插件配置(兼容旧版本)
|
||||
plugin_api_key = self.get_config("api.api_key", "")
|
||||
if not plugin_api_key:
|
||||
logger.warning("SiliconFlow API密钥未配置,请在全局配置 config/model_config.toml 中设置SiliconFlow API提供商")
|
||||
else:
|
||||
logger.info("检测到插件本地API密钥配置(建议迁移到全局配置)")
|
||||
else:
|
||||
logger.info("SiliconFlow API密钥配置检查通过")
|
||||
|
||||
# 你怎么知道我终于丢掉了我自己的脑子并使用了ai来帮我写代码的
|
||||
# 我也不知道,反正我现在就这样干了()
|
||||
|
||||
async def on_plugin_unload(self):
|
||||
"""插件卸载时的回调"""
|
||||
logger.info("SiliconFlow IndexTTS插件已卸载")
|
||||
169
src/plugins/built_in/siliconflow_api_index_tts/upload_voice.py
Normal file
169
src/plugins/built_in/siliconflow_api_index_tts/upload_voice.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SiliconFlow IndexTTS Voice Upload Tool
|
||||
用于上传参考音频文件并获取voice_id的工具脚本
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
import toml
|
||||
|
||||
|
||||
# 设置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VoiceUploader:
|
||||
"""语音上传器"""
|
||||
|
||||
def __init__(self, api_key: str):
|
||||
self.api_key = api_key
|
||||
self.upload_url = "https://api.siliconflow.cn/v1/uploads/audio/voice"
|
||||
|
||||
async def upload_audio(self, audio_path: str) -> str:
|
||||
"""
|
||||
上传音频文件并获取voice_id
|
||||
|
||||
Args:
|
||||
audio_path: 音频文件路径
|
||||
|
||||
Returns:
|
||||
voice_id: 返回的语音ID
|
||||
"""
|
||||
audio_path = Path(audio_path)
|
||||
if not audio_path.exists():
|
||||
raise FileNotFoundError(f"音频文件不存在: {audio_path}")
|
||||
|
||||
# 读取音频文件并转换为base64
|
||||
with open(audio_path, "rb") as f:
|
||||
audio_data = f.read()
|
||||
|
||||
audio_base64 = base64.b64encode(audio_data).decode('utf-8')
|
||||
|
||||
# 准备请求数据
|
||||
request_data = {
|
||||
"file": audio_base64,
|
||||
"filename": audio_path
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
logger.info(f"正在上传音频文件: {audio_path}")
|
||||
logger.info(f"文件大小: {len(audio_data)} bytes")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
self.upload_url,
|
||||
headers=headers,
|
||||
json=request_data,
|
||||
timeout=aiohttp.ClientTimeout(total=60)
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
result = await response.json()
|
||||
voice_id = result.get("id")
|
||||
if voice_id:
|
||||
logger.info(f"上传成功!获取到voice_id: {voice_id}")
|
||||
return voice_id
|
||||
else:
|
||||
logger.error(f"上传响应中没有找到voice_id: {result}")
|
||||
raise Exception("上传响应中没有找到voice_id")
|
||||
else:
|
||||
error_text = await response.text()
|
||||
logger.error(f"上传失败 (状态码: {response.status}): {error_text}")
|
||||
raise Exception(f"上传失败: {error_text}")
|
||||
|
||||
|
||||
def load_config(config_path: Path) -> dict:
|
||||
"""加载配置文件"""
|
||||
if config_path.exists():
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
return toml.load(f)
|
||||
return {}
|
||||
|
||||
|
||||
def save_config(config_path: Path, config: dict):
|
||||
"""保存配置文件"""
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
toml.dump(config, f)
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
if len(sys.argv) != 2:
|
||||
print("用法: python upload_voice.py <音频文件路径>")
|
||||
print("示例: python upload_voice.py refer.mp3")
|
||||
sys.exit(1)
|
||||
|
||||
audio_file = sys.argv[1]
|
||||
|
||||
# 获取插件目录
|
||||
plugin_dir = Path(__file__).parent
|
||||
|
||||
# 加载全局配置获取API key
|
||||
bot_dir = plugin_dir.parents[2] # 回到Bot目录
|
||||
global_config_path = bot_dir / "config" / "model_config.toml"
|
||||
|
||||
if not global_config_path.exists():
|
||||
logger.error(f"全局配置文件不存在: {global_config_path}")
|
||||
logger.error("请确保Bot/config/model_config.toml文件存在并配置了SiliconFlow API密钥")
|
||||
sys.exit(1)
|
||||
|
||||
global_config = load_config(global_config_path)
|
||||
|
||||
# 从api_providers中查找SiliconFlow的API密钥
|
||||
api_key = None
|
||||
api_providers = global_config.get("api_providers", [])
|
||||
for provider in api_providers:
|
||||
if provider.get("name") == "SiliconFlow":
|
||||
api_key = provider.get("api_key")
|
||||
break
|
||||
|
||||
if not api_key:
|
||||
logger.error("在全局配置中未找到SiliconFlow API密钥")
|
||||
logger.error("请在Bot/config/model_config.toml中添加SiliconFlow的api_providers配置:")
|
||||
logger.error("[[api_providers]]")
|
||||
logger.error("name = \"SiliconFlow\"")
|
||||
logger.error("base_url = \"https://api.siliconflow.cn/v1\"")
|
||||
logger.error("api_key = \"your_api_key_here\"")
|
||||
logger.error("client_type = \"openai\"")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
# 创建上传器并上传音频
|
||||
uploader = VoiceUploader(api_key)
|
||||
voice_id = await uploader.upload_audio(audio_file)
|
||||
|
||||
# 更新插件配置
|
||||
plugin_config_path = plugin_dir / "config.toml"
|
||||
plugin_config = load_config(plugin_config_path)
|
||||
|
||||
if "synthesis" not in plugin_config:
|
||||
plugin_config["synthesis"] = {}
|
||||
|
||||
plugin_config["synthesis"]["voice_id"] = voice_id
|
||||
|
||||
save_config(plugin_config_path, plugin_config)
|
||||
|
||||
logger.info(f"配置已更新!voice_id已保存到: {plugin_config_path}")
|
||||
logger.info("现在可以使用SiliconFlow IndexTTS插件了!")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"上传失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
110
src/plugins/phi_plugin/README.md
Normal file
110
src/plugins/phi_plugin/README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Phi Plugin for MoFox_Bot
|
||||
|
||||
基于MoFox_Bot插件系统的Phigros查分插件,移植自原phi-plugin项目。
|
||||
|
||||
## 插件化进展
|
||||
|
||||
### ✅ 已完成
|
||||
1. **基础架构搭建**
|
||||
- 创建了完整的插件目录结构
|
||||
- 实现了_manifest.json和config.toml配置文件
|
||||
- 建立了MoFox_Bot插件系统兼容的基础框架
|
||||
|
||||
2. **命令系统迁移**
|
||||
- 实现了5个核心命令的PlusCommand适配:
|
||||
- `phi help` - 帮助命令
|
||||
- `phi bind` - sessionToken绑定命令
|
||||
- `phi b30` - Best30查询命令
|
||||
- `phi info` - 个人信息查询命令
|
||||
- `phi score` - 单曲成绩查询命令
|
||||
|
||||
3. **数据管理模块**
|
||||
- 创建了PhiDataManager用于数据处理
|
||||
- 创建了PhiDatabaseManager用于数据库操作
|
||||
- 设计了统一的数据访问接口
|
||||
|
||||
4. **配置与元数据**
|
||||
- 符合MoFox_Bot规范的manifest文件
|
||||
- 支持功能开关的配置文件
|
||||
- 完整的插件依赖管理
|
||||
|
||||
### 🚧 待实现
|
||||
1. **核心功能逻辑**
|
||||
- Phigros API调用实现
|
||||
- sessionToken验证逻辑
|
||||
- 存档数据解析处理
|
||||
- B30等数据计算算法
|
||||
|
||||
2. **数据存储**
|
||||
- 用户token数据库存储
|
||||
- 曲库数据导入
|
||||
- 别名系统迁移
|
||||
|
||||
3. **图片生成**
|
||||
- B30成绩图片生成
|
||||
- 个人信息卡片生成
|
||||
- 单曲成绩展示图
|
||||
|
||||
4. **高级功能**
|
||||
- 更多原phi-plugin命令迁移
|
||||
- 数据缓存优化
|
||||
- 性能监控
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
src/plugins/phi_plugin/
|
||||
├── __init__.py # 插件初始化
|
||||
├── plugin.py # 主插件文件
|
||||
├── _manifest.json # 插件元数据
|
||||
├── config.toml # 插件配置
|
||||
├── README.md # 本文档
|
||||
├── commands/ # 命令实现
|
||||
│ ├── __init__.py
|
||||
│ ├── phi_help.py # 帮助命令
|
||||
│ ├── phi_bind.py # 绑定命令
|
||||
│ ├── phi_b30.py # B30查询
|
||||
│ ├── phi_info.py # 信息查询
|
||||
│ └── phi_score.py # 单曲成绩
|
||||
├── utils/ # 工具模块
|
||||
│ ├── __init__.py
|
||||
│ └── data_manager.py # 数据管理器
|
||||
├── data/ # 数据文件
|
||||
└── static/ # 静态资源
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 命令列表
|
||||
- `/phi help` - 查看帮助
|
||||
- `/phi bind <token>` - 绑定sessionToken
|
||||
- `/phi b30` - 查询Best30成绩
|
||||
- `/phi info [1|2]` - 查询个人信息
|
||||
- `/phi score <曲名>` - 查询单曲成绩
|
||||
|
||||
### 配置说明
|
||||
编辑 `config.toml` 文件可以调整:
|
||||
- 插件启用状态
|
||||
- API相关设置
|
||||
- 功能开关
|
||||
|
||||
## 技术特点
|
||||
|
||||
1. **架构兼容**:完全符合MoFox_Bot插件系统规范
|
||||
2. **命令适配**:使用PlusCommand系统,支持别名和参数解析
|
||||
3. **模块化设计**:清晰的模块分离,便于维护和扩展
|
||||
4. **异步处理**:全面使用async/await进行异步处理
|
||||
5. **错误处理**:完善的异常处理和用户提示
|
||||
|
||||
## 开发说明
|
||||
|
||||
目前插件已完成基础架构搭建,可以在MoFox_Bot中正常加载和注册命令。
|
||||
|
||||
下一步开发重点:
|
||||
1. 实现Phigros API调用逻辑
|
||||
2. 完成数据库存储功能
|
||||
3. 移植原插件的核心算法
|
||||
4. 实现图片生成功能
|
||||
|
||||
## 原始项目
|
||||
基于 [phi-plugin](https://github.com/Catrong/phi-plugin) 进行插件化改造。
|
||||
Reference in New Issue
Block a user