From d21c18f65742d3370a94bd7a3f0e94ff710e7354 Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Wed, 14 May 2025 22:43:40 +0800 Subject: [PATCH 01/13] change defualt.py --- astrbot/core/config/default.py | 11 ++++ .../core/provider/sources/volcengine_tts.py | 65 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 astrbot/core/provider/sources/volcengine_tts.py diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 5e06c19db..5eb0495a6 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -747,6 +747,17 @@ CONFIG_METADATA_2 = { "dashscope_tts_voice": "loongstella", "timeout": "20", }, + "火山引擎_TTS(API)": { + "id": "volcengine_tts", + "type": "volcengine_tts", + "enable": False, + "api_key": "", + "appid": "", + "cluster": "", + "voice_type": "xiaoyun", + "api_base": "https://openspeech.bytedance.com/api/v1/tts", + "timeout": "20", + }, }, "items": { "dashscope_tts_voice": { diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py new file mode 100644 index 000000000..76d08f078 --- /dev/null +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -0,0 +1,65 @@ +import uuid +import base64 +import json +import requests +from ..provider import TTSProvider +from ..entities import ProviderType +from ..register import register_provider_adapter + +@register_provider_adapter( + "volcengine_tts", "火山引擎 TTS", provider_type=ProviderType.TEXT_TO_SPEECH +) +class ProviderVolcengineTTS(TTSProvider): + def __init__(self, provider_config: dict, provider_settings: dict) -> None: + super().__init__(provider_config, provider_settings) + self.api_key = provider_config.get("api_key", "") + self.appid = provider_config.get("appid", "") + self.cluster = provider_config.get("cluster", "") + self.voice_type = provider_config.get("voice_type", "xiaoyun") + self.api_base = provider_config.get("api_base", "https://openspeech.bytedance.com/api/v1/tts") + self.timeout = provider_config.get("timeout", "20") + + def _build_request_payload(self, text: str) -> dict: + return { + "app": { + "appid": self.appid, + "token": self.api_key, + "cluster": self.cluster + }, + "user": { + "uid": str(uuid.uuid4()) + }, + "audio": { + "voice_type": self.voice_type, + "encoding": "mp3", + "speed_ratio": 1.0, + "volume_ratio": 1.0, + "pitch_ratio": 1.0, + }, + "request": { + "reqid": str(uuid.uuid4()), + "text": text, + "text_type": "plain", + "operation": "query", + "with_frontend": 1, + "frontend_type": "unitTson" + } + } + + def get_audio(self, text: str) -> str: + headers = {"Authorization": f"Bearer {self.api_key}"} + payload = self._build_request_payload(text) + response = requests.post(self.api_base, json=payload, headers=headers, timeout=self.timeout) + + if response.status_code == 200: + resp_data = response.json() + if "data" in resp_data: + audio_data = base64.b64decode(resp_data["data"]) + file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" + with open(file_path, "wb") as audio_file: + audio_file.write(audio_data) + return file_path + else: + raise Exception(f"火山引擎 TTS API 返回错误: {resp_data}") + else: + raise Exception(f"火山引擎 TTS API 请求失败: {response.status_code}, {response.text}") \ No newline at end of file From c75156c4ce18e9e2029a26e787441ce12625cdd5 Mon Sep 17 00:00:00 2001 From: Larch-C Date: Sat, 17 May 2025 18:08:55 +0800 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=86=E7=99=BB=E5=BD=95=E7=95=8C=E9=9D=A2=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/assets/images/astrbot_logo_mini.webp | Bin 0 -> 10668 bytes dashboard/src/components/shared/Logo.vue | 70 +++++++ .../views/authentication/auth/LoginPage.vue | 102 +++++++++- .../authentication/authForms/AuthLogin.vue | 179 +++++++++++++----- 4 files changed, 299 insertions(+), 52 deletions(-) create mode 100644 dashboard/src/assets/images/astrbot_logo_mini.webp create mode 100644 dashboard/src/components/shared/Logo.vue diff --git a/dashboard/src/assets/images/astrbot_logo_mini.webp b/dashboard/src/assets/images/astrbot_logo_mini.webp new file mode 100644 index 0000000000000000000000000000000000000000..f6a868c54357a3e42592a2b2d2c326e194176b27 GIT binary patch literal 10668 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 D2_=XN literal 0 HcmV?d00001 diff --git a/dashboard/src/components/shared/Logo.vue b/dashboard/src/components/shared/Logo.vue new file mode 100644 index 000000000..294fae094 --- /dev/null +++ b/dashboard/src/components/shared/Logo.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/dashboard/src/views/authentication/auth/LoginPage.vue b/dashboard/src/views/authentication/auth/LoginPage.vue index 63a1f809c..4bcefe07c 100644 --- a/dashboard/src/views/authentication/auth/LoginPage.vue +++ b/dashboard/src/views/authentication/auth/LoginPage.vue @@ -1,23 +1,111 @@ + From f67b9f5f6ec691bfd80557db67d8477e9a79a2cc Mon Sep 17 00:00:00 2001 From: Larch-C Date: Sat, 17 May 2025 18:09:49 +0800 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E4=BA=86=E5=A6=82=E6=9E=9C=E6=AD=A4=E5=89=8D=E5=B7=B2=E7=BB=8F?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=BD=86=E6=9C=AA=E8=87=AA=E8=A1=8C=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/router/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dashboard/src/router/index.ts b/dashboard/src/router/index.ts index a56154f18..c44e0249d 100644 --- a/dashboard/src/router/index.ts +++ b/dashboard/src/router/index.ts @@ -24,6 +24,11 @@ router.beforeEach(async (to, from, next) => { const authRequired = !publicPages.includes(to.path); const auth: AuthStore = useAuthStore(); + // 如果用户已登录且试图访问登录页面,则重定向到首页或之前尝试访问的页面 + if (to.path === '/auth/login' && auth.has_token()) { + return next(auth.returnUrl || '/'); + } + if (to.matched.some((record) => record.meta.requiresAuth)) { if (authRequired && !auth.has_token()) { auth.returnUrl = to.fullPath; From ac7f43520b5348caa885ced48d250da4b39e0a99 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 17 May 2025 21:30:05 +0800 Subject: [PATCH 04/13] =?UTF-8?q?=F0=9F=8E=88=20perf:=20adjust=20login=20i?= =?UTF-8?q?nput=20padding=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/views/authentication/authForms/AuthLogin.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dashboard/src/views/authentication/authForms/AuthLogin.vue b/dashboard/src/views/authentication/authForms/AuthLogin.vue index ff446ead0..b2b356087 100644 --- a/dashboard/src/views/authentication/authForms/AuthLogin.vue +++ b/dashboard/src/views/authentication/authForms/AuthLogin.vue @@ -104,7 +104,8 @@ async function validate(values: any, { setErrors }: any) { .input-field, .pwd-input { .v-field__field { - padding-top: 10px; + padding-top: 5px; + padding-bottom: 5px; } .v-field__outline { From db13a602742b2a2fe0d215f80cc6fe1fb5686359 Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Sun, 18 May 2025 03:18:36 +0800 Subject: [PATCH 05/13] =?UTF-8?q?=E2=9C=A8=20feat:=20add-volcengine-tts-su?= =?UTF-8?q?pport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 5 +- astrbot/core/provider/manager.py | 4 + .../core/provider/sources/volcengine_tts.py | 87 +++++++++++++++---- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index dd50b110f..526280130 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -803,13 +803,14 @@ CONFIG_METADATA_2 = { "火山引擎_TTS(API)": { "id": "volcengine_tts", "type": "volcengine_tts", + "provider_type": "text_to_speech", "enable": False, "api_key": "", "appid": "", "cluster": "", - "voice_type": "xiaoyun", + "voice_type": "", "api_base": "https://openspeech.bytedance.com/api/v1/tts", - "timeout": "20", + "timeout": 20, }, }, "items": { diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index e61fbf925..80269752d 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -206,6 +206,10 @@ class ProviderManager: from .sources.azure_tts_source import ( AzureTTSProvider as AzureTTSProvider, ) + case "volcengine_tts": + from .sources.volcengine_tts import ( + ProviderVolcengineTTS as ProviderVolcengineTTS, + ) except (ImportError, ModuleNotFoundError) as e: logger.critical( f"加载 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}。可能是因为有未安装的依赖。" diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py index 76d08f078..90680f42b 100644 --- a/astrbot/core/provider/sources/volcengine_tts.py +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -1,6 +1,10 @@ import uuid import base64 import json +import os +import traceback +import asyncio +import aiohttp import requests from ..provider import TTSProvider from ..entities import ProviderType @@ -16,8 +20,11 @@ class ProviderVolcengineTTS(TTSProvider): self.appid = provider_config.get("appid", "") self.cluster = provider_config.get("cluster", "") self.voice_type = provider_config.get("voice_type", "xiaoyun") - self.api_base = provider_config.get("api_base", "https://openspeech.bytedance.com/api/v1/tts") - self.timeout = provider_config.get("timeout", "20") + + host = "openspeech.bytedance.com" + self.api_base = provider_config.get("api_base", f"https://{host}/api/v1/tts") + + self.timeout = provider_config.get("timeout", 20) def _build_request_payload(self, text: str) -> dict: return { @@ -27,7 +34,7 @@ class ProviderVolcengineTTS(TTSProvider): "cluster": self.cluster }, "user": { - "uid": str(uuid.uuid4()) + "uid": str(uuid.uuid4()) }, "audio": { "voice_type": self.voice_type, @@ -46,20 +53,62 @@ class ProviderVolcengineTTS(TTSProvider): } } - def get_audio(self, text: str) -> str: - headers = {"Authorization": f"Bearer {self.api_key}"} + async def get_audio(self, text: str) -> str: + """异步方法获取语音文件路径""" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer;{self.api_key}" + } + payload = self._build_request_payload(text) - response = requests.post(self.api_base, json=payload, headers=headers, timeout=self.timeout) - - if response.status_code == 200: - resp_data = response.json() - if "data" in resp_data: - audio_data = base64.b64decode(resp_data["data"]) - file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" - with open(file_path, "wb") as audio_file: - audio_file.write(audio_data) - return file_path - else: - raise Exception(f"火山引擎 TTS API 返回错误: {resp_data}") - else: - raise Exception(f"火山引擎 TTS API 请求失败: {response.status_code}, {response.text}") \ No newline at end of file + + # 打印请求信息以便调试 + print(f"请求 URL: {self.api_base}") + print(f"请求头: {headers}") + print(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") + + try: + # 使用 aiohttp 进行异步请求 + async with aiohttp.ClientSession() as session: + async with session.post( + self.api_base, + data=json.dumps(payload), # 使用 data 而不是 json 参数 + headers=headers, + timeout=self.timeout + ) as response: + print(f"响应状态码: {response.status}") + + # 获取响应内容 + response_text = await response.text() + print(f"响应内容: {response_text[:200]}...") + + if response.status == 200: + resp_data = json.loads(response_text) + + if "data" in resp_data: + audio_data = base64.b64decode(resp_data["data"]) + + # 确保目录存在 + os.makedirs("data/temp", exist_ok=True) + + file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" + + # 使用线程运行I/O操作,避免阻塞 + loop = asyncio.get_running_loop() + await loop.run_in_executor( + None, + lambda: open(file_path, "wb").write(audio_data) + ) + + return file_path + else: + error_msg = resp_data.get("message", "未知错误") + raise Exception(f"火山引擎 TTS API 返回错误: {error_msg}") + else: + raise Exception(f"火山引擎 TTS API 请求失败: {response.status}, {response_text}") + + except Exception as e: + # 添加更详细的异常捕获 + error_details = traceback.format_exc() + print(f"火山引擎 TTS 异常详情: {error_details}") + raise Exception(f"火山引擎 TTS 异常: {str(e)}") \ No newline at end of file From 1ae78ca98c2f47ef5887faeb5c2ed07ec128b35c Mon Sep 17 00:00:00 2001 From: HendricksJudy <61645034+HendricksJudy@users.noreply.github.com> Date: Sun, 18 May 2025 02:30:31 -0700 Subject: [PATCH 06/13] chore: fix lint issues --- astrbot/core/provider/sources/azure_tts_source.py | 8 ++++---- packages/vpet/main.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/astrbot/core/provider/sources/azure_tts_source.py b/astrbot/core/provider/sources/azure_tts_source.py index 18d9bfbac..c35c7ec6c 100644 --- a/astrbot/core/provider/sources/azure_tts_source.py +++ b/astrbot/core/provider/sources/azure_tts_source.py @@ -53,8 +53,8 @@ class OTTSProvider: async def _generate_signature(self) -> str: await self._sync_time() timestamp = int(time.time()) + self.time_offset - nonce = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=10)) - path = re.sub(r'^https?://[^/]+', '', self.api_url) or '/' + nonce = "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=10)) + path = re.sub(r"^https?://[^/]+", "", self.api_url) or "/" return f"{timestamp}-{nonce}-0-{hashlib.md5(f'{path}-{timestamp}-{nonce}-0-{self.skey}'.encode()).hexdigest()}" async def get_audio(self, text: str, voice_params: Dict) -> str: @@ -92,7 +92,7 @@ class AzureNativeProvider(TTSProvider): def __init__(self, provider_config: dict, provider_settings: dict): super().__init__(provider_config, provider_settings) self.subscription_key = provider_config.get("azure_tts_subscription_key", "").strip() - if not re.fullmatch(r'^[a-zA-Z0-9]{32}$', self.subscription_key): + if not re.fullmatch(r"^[a-zA-Z0-9]{32}$", self.subscription_key): raise ValueError("无效的Azure订阅密钥") self.region = provider_config.get("azure_tts_region", "eastus").strip() self.endpoint = f"https://{self.region}.tts.speech.microsoft.com/cognitiveservices/v1" @@ -188,7 +188,7 @@ class AzureTTSProvider(TTSProvider): raise ValueError(error_msg) from e except KeyError as e: raise ValueError(f"配置错误: 缺少必要参数 {e}") from e - if re.fullmatch(r'^[a-zA-Z0-9]{32}$', key_value): + if re.fullmatch(r"^[a-zA-Z0-9]{32}$", key_value): return AzureNativeProvider(config, self.provider_settings) raise ValueError("订阅密钥格式无效,应为32位字母数字或other[...]格式") diff --git a/packages/vpet/main.py b/packages/vpet/main.py index 6623cd6f9..d345a5ac8 100644 --- a/packages/vpet/main.py +++ b/packages/vpet/main.py @@ -1,6 +1,5 @@ from astrbot.api.event import filter, AstrMessageEvent from astrbot.api.star import Context, Star, register -from astrbot.api import logger @register("vpet", "AstrBot Team", "虚拟桌宠", "0.0.1") class VPet(Star): From bf306a2f01d86891293134138c7da3a63ba195bd Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Mon, 19 May 2025 16:16:25 +0800 Subject: [PATCH 07/13] =?UTF-8?q?=F0=9F=A9=B9fix:=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0logger=E5=87=BD=E6=95=B0,=E6=B7=BB=E5=8A=A0sp?= =?UTF-8?q?eed=5Fratio=E9=80=89=E9=A1=B9,=E4=B8=BA=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E6=B7=BB=E5=8A=A0description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 25 +++++++++++-- .../core/provider/sources/volcengine_tts.py | 35 ++++++++----------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index a47e9a4c9..cf8e68862 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -848,13 +848,34 @@ CONFIG_METADATA_2 = { "enable": False, "api_key": "", "appid": "", - "cluster": "", - "voice_type": "", + "volcengine_cluster": "", + "volcengine_voice_type": "", + "volcengine_speed_ratio": 1.0, "api_base": "https://openspeech.bytedance.com/api/v1/tts", "timeout": 20, }, }, "items": { + "volcengine_cluster": { + "type": "string", + "description": "火山引擎集群", + "hint": "可选volcano_icl或volcano_icl_concurr" + }, + "volcengine_voice_type": { + "type": "string", + "description": "火山引擎音色", + "hint": "输入S_开头的声音id(SpeakerId)" + }, + "volcengine_speed_ratio": { + "type": "float", + "description": "语速设置", + "hint": "语速设置,范围为 0.2 到 3.0,默认值为 1.0" + }, + "volcengine_volume_ratio": { + "type": "float", + "description": "音量设置", + "hint": "音量设置,范围为 0.0 到 2.0,默认值为 1.0" + }, "azure_tts_voice": { "type": "string", "description": "音色设置", diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py index 90680f42b..e713345b4 100644 --- a/astrbot/core/provider/sources/volcengine_tts.py +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -9,6 +9,7 @@ import requests from ..provider import TTSProvider from ..entities import ProviderType from ..register import register_provider_adapter +from astrbot import logger @register_provider_adapter( "volcengine_tts", "火山引擎 TTS", provider_type=ProviderType.TEXT_TO_SPEECH @@ -18,12 +19,10 @@ class ProviderVolcengineTTS(TTSProvider): super().__init__(provider_config, provider_settings) self.api_key = provider_config.get("api_key", "") self.appid = provider_config.get("appid", "") - self.cluster = provider_config.get("cluster", "") - self.voice_type = provider_config.get("voice_type", "xiaoyun") - - host = "openspeech.bytedance.com" - self.api_base = provider_config.get("api_base", f"https://{host}/api/v1/tts") - + self.cluster = provider_config.get("volcengine_cluster", "") + self.voice_type = provider_config.get("volcengine_voice_type", "") + self.speed_ratio = provider_config.get("volcengine_speed_ratio", 1.0) + self.api_base = provider_config.get("api_base", f"https://openspeech.bytedance.com/api/v1/tts") self.timeout = provider_config.get("timeout", 20) def _build_request_payload(self, text: str) -> dict: @@ -39,7 +38,7 @@ class ProviderVolcengineTTS(TTSProvider): "audio": { "voice_type": self.voice_type, "encoding": "mp3", - "speed_ratio": 1.0, + "speed_ratio": self.speed_ratio, "volume_ratio": 1.0, "pitch_ratio": 1.0, }, @@ -57,30 +56,27 @@ class ProviderVolcengineTTS(TTSProvider): """异步方法获取语音文件路径""" headers = { "Content-Type": "application/json", - "Authorization": f"Bearer;{self.api_key}" + "Authorization": f"Bearer; {self.api_key}" } payload = self._build_request_payload(text) - # 打印请求信息以便调试 - print(f"请求 URL: {self.api_base}") - print(f"请求头: {headers}") - print(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") + logger.info(f"请求头: {headers}") + logger.info(f"请求 URL: {self.api_base}") + logger.info(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") try: - # 使用 aiohttp 进行异步请求 async with aiohttp.ClientSession() as session: async with session.post( self.api_base, - data=json.dumps(payload), # 使用 data 而不是 json 参数 + data=json.dumps(payload), headers=headers, timeout=self.timeout ) as response: - print(f"响应状态码: {response.status}") + logger.info(f"响应状态码: {response.status}") - # 获取响应内容 response_text = await response.text() - print(f"响应内容: {response_text[:200]}...") + logger.info(f"响应内容: {response_text[:200]}...") if response.status == 200: resp_data = json.loads(response_text) @@ -88,12 +84,10 @@ class ProviderVolcengineTTS(TTSProvider): if "data" in resp_data: audio_data = base64.b64decode(resp_data["data"]) - # 确保目录存在 os.makedirs("data/temp", exist_ok=True) file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" - # 使用线程运行I/O操作,避免阻塞 loop = asyncio.get_running_loop() await loop.run_in_executor( None, @@ -108,7 +102,6 @@ class ProviderVolcengineTTS(TTSProvider): raise Exception(f"火山引擎 TTS API 请求失败: {response.status}, {response_text}") except Exception as e: - # 添加更详细的异常捕获 error_details = traceback.format_exc() - print(f"火山引擎 TTS 异常详情: {error_details}") + logger.info(f"火山引擎 TTS 异常详情: {error_details}") raise Exception(f"火山引擎 TTS 异常: {str(e)}") \ No newline at end of file From 205180387a258c7d37cf6ce53d6bb43f98f65093 Mon Sep 17 00:00:00 2001 From: MiSeya <1710669647@qq> Date: Mon, 19 May 2025 21:12:09 +0800 Subject: [PATCH 08/13] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=E4=BA=86WeChatPadP?= =?UTF-8?q?ro=E5=9C=A8=E9=87=8D=E6=96=B0=E7=99=BB=E5=BD=95=E6=97=B6?= =?UTF-8?q?=E4=B8=BA=E6=96=B0=E8=AE=BE=E5=A4=87=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E5=BB=B6=E9=95=BF=E5=88=9D=E5=A7=8B=E5=8C=96Auth=5FKe?= =?UTF-8?q?y=E6=9C=89=E6=95=88=E6=9C=9F=E8=87=B3365=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wechatpadpro/wechatpadpro_adapter.py | 72 +++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py b/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py index 47d4fa9df..2e2ab3878 100644 --- a/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +++ b/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py @@ -69,39 +69,42 @@ class WeChatPadProAdapter(Platform): self.auth_key = loaded_credentials.get("auth_key") self.wxid = loaded_credentials.get("wxid") + isLoginIn = await self.check_online_status() + # 检查在线状态 - if self.auth_key and await self.check_online_status(): - logger.info("WeChatPadPro 设备已在线,跳过扫码登录。") + if self.auth_key and isLoginIn: + logger.info("WeChatPadPro 设备已在线,凭据存在,跳过扫码登录。") # 如果在线,连接 WebSocket 接收消息 self.ws_handle_task = asyncio.create_task(self.connect_websocket()) else: - logger.info("WeChatPadPro 设备不在线或无可用凭据,开始扫码登录流程。") # 1. 生成授权码 - await self.generate_auth_key() - if not self.auth_key: - logger.error("无法获取授权码,WeChatPadPro 适配器启动失败。") - return + logger.info("WeChatPadPro 无可用凭据,将生成新的授权码。") + await self.generate_auth_key() # 2. 获取登录二维码 - qr_code_url = await self.get_login_qr_code() + if not isLoginIn: + logger.info("WeChatPadPro 设备已离线,开始扫码登录。") + qr_code_url = await self.get_login_qr_code() - if qr_code_url: - logger.info(f"请扫描以下二维码登录: {qr_code_url}") - else: - logger.error("无法获取登录二维码。") - return + if qr_code_url: + logger.info(f"请扫描以下二维码登录: {qr_code_url}") + else: + logger.error("无法获取登录二维码。") + return - # 3. 检测扫码状态 - login_successful = await self.check_login_status() + # 3. 检测扫码状态 + login_successful = await self.check_login_status() - if login_successful: - # 登录成功后,连接 WebSocket 接收消息 - self.ws_handle_task = asyncio.create_task(self.connect_websocket()) - else: - logger.warning("登录失败或超时,WeChatPadPro 适配器将关闭。") - await self.terminate() - return + if login_successful: + logger.info("登录成功,WeChatPadPro适配器已连接。") + else: + logger.warning("登录失败或超时,WeChatPadPro 适配器将关闭。") + await self.terminate() + return + + # 登录成功后,连接 WebSocket 接收消息 + self.ws_handle_task = asyncio.create_task(self.connect_websocket()) self._shutdown_event = asyncio.Event() await self._shutdown_event.wait() @@ -156,16 +159,29 @@ class WeChatPadProAdapter(Platform): if login_state == 1: logger.info("WeChatPadPro 设备当前在线。") return True - else: + # login_state == 3 为离线状态 + elif login_state == 3: logger.info( - f"WeChatPadPro 设备不在线,登录状态: {login_state}" + "WeChatPadPro 设备不在线。" ) return False + else: + logger.error( + f"未知的在线状态: {login_state:}" + ) + return False + # Code == 300 为微信退出状态。 + elif response.status == 200 and response_data.get("Code") == 300: + logger.info( + "WeChatPadPro 设备已退出。" + ) + return False else: logger.error( f"检查在线状态失败: {response.status}, {response_data}" ) return False + except aiohttp.ClientConnectorError as e: logger.error(f"连接到 WeChatPadPro 服务失败: {e}") return False @@ -179,7 +195,7 @@ class WeChatPadProAdapter(Platform): """ url = f"{self.base_url}/admin/GenAuthKey1" params = {"key": self.admin_key} - payload = {"Count": 1, "Days": 30} # 生成一个有效期30天的授权码 + payload = {"Count": 1, "Days": 365} # 生成一个有效期365天的授权码 async with aiohttp.ClientSession() as session: try: @@ -336,7 +352,7 @@ class WeChatPadProAdapter(Platform): message = await asyncio.wait_for( websocket.recv(), timeout=wait_time ) - logger.info(message) + # logger.debug(message) # 不显示原始消息内容 asyncio.create_task(self.handle_websocket_message(message)) except asyncio.TimeoutError: logger.warning( @@ -350,7 +366,7 @@ class WeChatPadProAdapter(Platform): logger.error(f"处理 WebSocket 消息时发生错误: {e}") break except Exception as e: - logger.error(f"WebSocket 连接失败: {e}") + logger.error(f"WebSocket 连接失败: {e}, 请检查WeChatPadPro服务状态,或尝试重启WeChatPadPro适配器。") await asyncio.sleep(5) async def handle_websocket_message(self, message: str): @@ -425,7 +441,7 @@ class WeChatPadProAdapter(Platform): ): # 再根据消息类型处理消息内容 await self._process_message_content(abm, raw_message, msg_type, content) - + return abm return None From d09b70b295be88452040c3eb4fd52455cdb0692a Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Tue, 20 May 2025 01:38:13 -0400 Subject: [PATCH 09/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=85=AC=E4=BC=97=E5=8F=B7=EF=BC=88=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=EF=BC=89=E4=B8=8B=E6=97=A0=E6=B3=95=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=E6=B6=88=E6=81=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 6 ++ .../weixin_offacc_adapter.py | 52 +++++++++++++---- .../weixin_offacc_event.py | 56 ++++++++++++++++--- packages/astrbot/main.py | 6 ++ 4 files changed, 101 insertions(+), 19 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index f05e17f43..c78bd0480 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -176,6 +176,7 @@ CONFIG_METADATA_2 = { "api_base_url": "https://api.weixin.qq.com/cgi-bin/", "callback_server_host": "0.0.0.0", "port": 6194, + "active_send_mode": False }, "wecom(企业微信)": { "id": "wecom", @@ -220,6 +221,11 @@ CONFIG_METADATA_2 = { }, }, "items": { + "active_send_mode": { + "description": "是否换用主动发送接口", + "type": "bool", + "desc": "只有企业认证的公众号才能主动发送。主动发送接口的限制会少一些。" + }, "wpp_active_message_poll": { "description": "是否启用主动消息轮询", "type": "bool", diff --git a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py index 5ed589516..04186ff9d 100644 --- a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +++ b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py @@ -20,7 +20,7 @@ from requests import Response from wechatpy.utils import check_signature from wechatpy.crypto import WeChatCrypto from wechatpy import WeChatClient -from wechatpy.messages import TextMessage, ImageMessage, VoiceMessage +from wechatpy.messages import TextMessage, ImageMessage, VoiceMessage, BaseMessage from wechatpy.exceptions import InvalidSignatureException from wechatpy import parse_message from .weixin_offacc_event import WeixinOfficialAccountPlatformEvent @@ -87,7 +87,11 @@ class WecomServer: logger.info(f"解析成功: {msg}") if self.callback: - await self.callback(msg) + result_xml = await self.callback(msg) + if not result_xml: + return "success" + if isinstance(result_xml, str): + return result_xml return "success" @@ -117,6 +121,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform): self.api_base_url = platform_config.get( "api_base_url", "https://api.weixin.qq.com/cgi-bin/" ) + self.active_send_mode = self.config.get("active_send_mode", False) if not self.api_base_url: self.api_base_url = "https://api.weixin.qq.com/cgi-bin/" @@ -138,9 +143,29 @@ class WeixinOfficialAccountPlatformAdapter(Platform): self.client.API_BASE_URL = self.api_base_url - async def callback(msg): + # 微信公众号必须 5 秒内进行回复,否则会重试 3 次,我们需要对其进行消息排重 + # msgid -> Future + self.wexin_event_workers: dict[str, asyncio.Future] = {} + + async def callback(msg: BaseMessage): try: - await self.convert_message(msg) + if self.active_send_mode: + await self.convert_message(msg, None) + else: + if msg.id in self.wexin_event_workers: + future = self.wexin_event_workers[msg.id] + logger.debug(f"duplicate message id checked: {msg.id}") + else: + future = asyncio.get_event_loop().create_future() + self.wexin_event_workers[msg.id] = future + await self.convert_message(msg, future) + # I love shield so much! + result = await asyncio.wait_for(asyncio.shield(future), 60) # wait for 60s + logger.debug(f"Got future result: {result}") + self.wexin_event_workers.pop(msg.id, None) + return result # xml. see weixin_offacc_event.py + except asyncio.TimeoutError: + pass except Exception as e: logger.error(f"转换消息时出现异常: {e}") @@ -163,7 +188,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform): async def run(self): await self.server.start_polling() - async def convert_message(self, msg) -> AstrBotMessage | None: + async def convert_message( + self, msg, future: asyncio.Future = None + ) -> AstrBotMessage | None: abm = AstrBotMessage() if isinstance(msg, TextMessage): abm.message_str = msg.content @@ -177,7 +204,6 @@ class WeixinOfficialAccountPlatformAdapter(Platform): abm.message_id = msg.id abm.timestamp = msg.time abm.session_id = abm.sender.user_id - abm.raw_message = msg elif msg.type == "image": assert isinstance(msg, ImageMessage) abm.message_str = "[图片]" @@ -191,7 +217,6 @@ class WeixinOfficialAccountPlatformAdapter(Platform): abm.message_id = msg.id abm.timestamp = msg.time abm.session_id = abm.sender.user_id - abm.raw_message = msg elif msg.type == "voice": assert isinstance(msg, VoiceMessage) @@ -209,7 +234,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform): audio = AudioSegment.from_file(path) audio.export(path_wav, format="wav") except Exception as e: - logger.error(f"转换音频失败: {e}。如果没有安装 pydub 和 ffmpeg 请先安装。") + logger.error( + f"转换音频失败: {e}。如果没有安装 pydub 和 ffmpeg 请先安装。" + ) path_wav = path return @@ -224,11 +251,16 @@ class WeixinOfficialAccountPlatformAdapter(Platform): abm.message_id = msg.id abm.timestamp = msg.time abm.session_id = abm.sender.user_id - abm.raw_message = msg else: logger.warning(f"暂未实现的事件: {msg.type}") + future.set_result(None) return - + # 很不优雅 :( + abm.raw_message = { + "message": msg, + "future": future, + "active_send_mode": self.active_send_mode, + } logger.info(f"abm: {abm}") await self.handle_msg(abm) diff --git a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py index 9519cd497..102812705 100644 --- a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +++ b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py @@ -4,6 +4,8 @@ from astrbot.api.event import AstrMessageEvent, MessageChain from astrbot.api.platform import AstrBotMessage, PlatformMetadata from astrbot.api.message_components import Plain, Image, Record from wechatpy import WeChatClient +from wechatpy.replies import TextReply, ImageReply, VoiceReply + from astrbot.api import logger @@ -82,12 +84,23 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent): async def send(self, message: MessageChain): message_obj = self.message_obj + active_send_mode = message_obj.raw_message.get("active_send_mode", False) for comp in message.chain: if isinstance(comp, Plain): # Split long text messages if needed plain_chunks = await self.split_plain(comp.text) for chunk in plain_chunks: - self.client.message.send_text(message_obj.sender.user_id, chunk) + if active_send_mode: + self.client.message.send_text(message_obj.sender.user_id, chunk) + else: + reply = TextReply( + content=chunk, + message=self.message_obj.raw_message["message"], + ) + xml = reply.render() + future = self.message_obj.raw_message["future"] + assert isinstance(future, asyncio.Future) + future.set_result(xml) await asyncio.sleep(0.5) # Avoid sending too fast elif isinstance(comp, Image): img_path = await comp.convert_to_file_path() @@ -102,10 +115,22 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent): ) return logger.debug(f"微信公众平台上传图片返回: {response}") - self.client.message.send_image( - message_obj.sender.user_id, - response["media_id"], - ) + + if active_send_mode: + self.client.message.send_image( + message_obj.sender.user_id, + response["media_id"], + ) + else: + reply = ImageReply( + media_id=response["media_id"], + message=self.message_obj.raw_message["message"], + ) + xml = reply.render() + future = self.message_obj.raw_message["future"] + assert isinstance(future, asyncio.Future) + future.set_result(xml) + elif isinstance(comp, Record): record_path = await comp.convert_to_file_path() # 转成amr @@ -124,10 +149,23 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent): ) return logger.info(f"微信公众平台上传语音返回: {response}") - self.client.message.send_voice( - message_obj.sender.user_id, - response["media_id"], - ) + + + if active_send_mode: + self.client.message.send_voice( + message_obj.sender.user_id, + response["media_id"], + ) + else: + reply = VoiceReply( + media_id=response["media_id"], + message=self.message_obj.raw_message["message"], + ) + xml = reply.render() + future = self.message_obj.raw_message["future"] + assert isinstance(future, asyncio.Future) + future.set_result(xml) + else: logger.warning(f"还没实现这个消息类型的发送逻辑: {comp.type}。") diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index 04d4cadb7..92ef6bfea 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -1462,3 +1462,9 @@ UID: {user_id} 此 ID 可用于设置管理员。 plugin_cfg["reset"] = reset_cfg alter_cmd_cfg["astrbot"] = plugin_cfg sp.put("alter_cmd", alter_cmd_cfg) + + @filter.command("test") + async def test_to(self, event: AstrMessageEvent): + import asyncio + await asyncio.sleep(10) + yield event.plain_result("OK") From 56bf5d38a12e6e1fe2d2068c562d49b55edb38b8 Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Tue, 20 May 2025 13:51:11 +0800 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=94=A7fix:=20=E4=BF=AE=E6=94=B9logg?= =?UTF-8?q?er=E8=BE=93=E5=87=BA=E7=AD=89=E7=BA=A7=E4=B8=BAdebug=E7=BA=A7?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/volcengine_tts.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py index e713345b4..dca0196b1 100644 --- a/astrbot/core/provider/sources/volcengine_tts.py +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -61,9 +61,9 @@ class ProviderVolcengineTTS(TTSProvider): payload = self._build_request_payload(text) - logger.info(f"请求头: {headers}") - logger.info(f"请求 URL: {self.api_base}") - logger.info(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") + logger.debug(f"请求头: {headers}") + logger.debug(f"请求 URL: {self.api_base}") + logger.debug(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") try: async with aiohttp.ClientSession() as session: @@ -73,10 +73,10 @@ class ProviderVolcengineTTS(TTSProvider): headers=headers, timeout=self.timeout ) as response: - logger.info(f"响应状态码: {response.status}") + logger.debug(f"响应状态码: {response.status}") response_text = await response.text() - logger.info(f"响应内容: {response_text[:200]}...") + logger.debug(f"响应内容: {response_text[:200]}...") if response.status == 200: resp_data = json.loads(response_text) @@ -103,5 +103,5 @@ class ProviderVolcengineTTS(TTSProvider): except Exception as e: error_details = traceback.format_exc() - logger.info(f"火山引擎 TTS 异常详情: {error_details}") + logger.debug(f"火山引擎 TTS 异常详情: {error_details}") raise Exception(f"火山引擎 TTS 异常: {str(e)}") \ No newline at end of file From f62157be722c93e565c5a124a6f76c43c1bdc35c Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Tue, 20 May 2025 02:00:54 -0400 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=93=A6=20release:=20v3.5.11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 2 +- changelogs/v3.5.11.md | 7 +++++++ pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 changelogs/v3.5.11.md diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index e3e1a7c2f..dfccf2ac1 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -5,7 +5,7 @@ import os from astrbot.core.utils.astrbot_path import get_astrbot_data_path -VERSION = "3.5.10" +VERSION = "3.5.11" DB_PATH = os.path.join(get_astrbot_data_path(), "data_v3.db") # 默认配置 diff --git a/changelogs/v3.5.11.md b/changelogs/v3.5.11.md new file mode 100644 index 000000000..9550e26c5 --- /dev/null +++ b/changelogs/v3.5.11.md @@ -0,0 +1,7 @@ +# What's Changed + +1. 新增:火山引擎 TTS +2. 修复:修复了 WeChatPadPro 在重新登录时为新设备的问题 +2. ‼️修复:微信公众号(个人认证或者未认证)的情况下能接收但无法回复消息的问题 +3. 修复:Minimax TTS 相关问题 +4. 优化:登录界面侧边栏、关于页面样式,修复如果此前已经登录但未自行跳转的问题 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7b38e8b43..f9971501c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "AstrBot" -version = "3.5.10" +version = "3.5.11" description = "易上手的多平台 LLM 聊天机器人及开发框架" readme = "README.md" requires-python = ">=3.10" From ca1aec892006c71cb873961bc0e9635a69e2e0c3 Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Thu, 22 May 2025 08:09:36 +0800 Subject: [PATCH 12/13] =?UTF-8?q?=F0=9F=90=9B=20fix=20:=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9description,=E9=80=82=E9=85=8D=E7=81=AB=E5=B1=B1?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E5=9F=BA=E7=A1=80=E7=9A=84=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E5=90=88=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index cf8e68862..577572186 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -848,7 +848,7 @@ CONFIG_METADATA_2 = { "enable": False, "api_key": "", "appid": "", - "volcengine_cluster": "", + "volcengine_cluster": "volcano_tts", "volcengine_voice_type": "", "volcengine_speed_ratio": 1.0, "api_base": "https://openspeech.bytedance.com/api/v1/tts", @@ -859,12 +859,12 @@ CONFIG_METADATA_2 = { "volcengine_cluster": { "type": "string", "description": "火山引擎集群", - "hint": "可选volcano_icl或volcano_icl_concurr" + "hint": "若使用语音复刻大模型,可选volcano_icl或volcano_icl_concurr,默认使用volcano_tts" }, "volcengine_voice_type": { "type": "string", "description": "火山引擎音色", - "hint": "输入S_开头的声音id(SpeakerId)" + "hint": "输入声音id(Voice_type)" }, "volcengine_speed_ratio": { "type": "float", From 8023557d6e5587f20efcbd4b2d583b18715d6241 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Thu, 22 May 2025 18:30:29 +0800 Subject: [PATCH 13/13] =?UTF-8?q?feat:=20=E5=BC=BA=E5=88=B6=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E9=BB=98=E8=AE=A4=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/layouts/full/vertical-header/VerticalHeader.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index 2a871485e..3ed90cf77 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -383,7 +383,7 @@ if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('cha -
为了安全,请尽快修改默认密码。
+
为了安全,请务必修改默认密码。
- + 关闭