AstrBot 4.7.4__py3-none-any.whl → 4.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/cli/__init__.py +1 -1
- astrbot/core/astr_agent_run_util.py +15 -1
- astrbot/core/config/default.py +58 -1
- astrbot/core/db/__init__.py +30 -1
- astrbot/core/db/sqlite.py +55 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
- astrbot/core/platform/manager.py +67 -9
- astrbot/core/platform/platform.py +99 -2
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +4 -3
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +4 -6
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +1 -2
- astrbot/core/platform/sources/lark/lark_adapter.py +1 -3
- astrbot/core/platform/sources/misskey/misskey_adapter.py +1 -2
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +2 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +1 -3
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +32 -9
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +13 -1
- astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
- astrbot/core/platform/sources/slack/client.py +50 -39
- astrbot/core/platform/sources/slack/slack_adapter.py +21 -7
- astrbot/core/platform/sources/slack/slack_event.py +3 -3
- astrbot/core/platform/sources/telegram/tg_adapter.py +1 -2
- astrbot/core/platform/sources/webchat/webchat_adapter.py +95 -29
- astrbot/core/platform/sources/webchat/webchat_event.py +33 -33
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +1 -2
- astrbot/core/platform/sources/wecom/wecom_adapter.py +51 -9
- astrbot/core/platform/sources/wecom/wecom_event.py +1 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +52 -11
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +1 -1
- astrbot/core/platform_message_history_mgr.py +3 -3
- astrbot/core/provider/sources/whisper_api_source.py +43 -11
- astrbot/core/utils/tencent_record_helper.py +1 -1
- astrbot/core/utils/webhook_utils.py +47 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +300 -70
- astrbot/dashboard/routes/config.py +19 -0
- astrbot/dashboard/routes/knowledge_base.py +1 -1
- astrbot/dashboard/routes/platform.py +100 -0
- astrbot/dashboard/server.py +3 -1
- {astrbot-4.7.4.dist-info → astrbot-4.8.0.dist-info}/METADATA +48 -37
- {astrbot-4.7.4.dist-info → astrbot-4.8.0.dist-info}/RECORD +46 -44
- {astrbot-4.7.4.dist-info → astrbot-4.8.0.dist-info}/WHEEL +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.8.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
4
|
import uuid
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
import quart
|
|
7
8
|
from requests import Response
|
|
@@ -24,6 +25,7 @@ from astrbot.api.platform import (
|
|
|
24
25
|
from astrbot.core import logger
|
|
25
26
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
26
27
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
28
|
+
from astrbot.core.utils.webhook_utils import log_webhook_info
|
|
27
29
|
|
|
28
30
|
from .wecom_event import WecomPlatformEvent
|
|
29
31
|
from .wecom_kf import WeChatKF
|
|
@@ -62,8 +64,20 @@ class WecomServer:
|
|
|
62
64
|
self.shutdown_event = asyncio.Event()
|
|
63
65
|
|
|
64
66
|
async def verify(self):
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
"""内部服务器的 GET 验证入口"""
|
|
68
|
+
return await self.handle_verify(quart.request)
|
|
69
|
+
|
|
70
|
+
async def handle_verify(self, request) -> str:
|
|
71
|
+
"""处理验证请求,可被统一 webhook 入口复用
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
request: Quart 请求对象
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
验证响应
|
|
78
|
+
"""
|
|
79
|
+
logger.info(f"验证请求有效性: {request.args}")
|
|
80
|
+
args = request.args
|
|
67
81
|
try:
|
|
68
82
|
echo_str = self.crypto.check_signature(
|
|
69
83
|
args.get("msg_signature"),
|
|
@@ -78,10 +92,22 @@ class WecomServer:
|
|
|
78
92
|
raise
|
|
79
93
|
|
|
80
94
|
async def callback_command(self):
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
"""内部服务器的 POST 回调入口"""
|
|
96
|
+
return await self.handle_callback(quart.request)
|
|
97
|
+
|
|
98
|
+
async def handle_callback(self, request) -> str:
|
|
99
|
+
"""处理回调请求,可被统一 webhook 入口复用
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
request: Quart 请求对象
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
响应内容
|
|
106
|
+
"""
|
|
107
|
+
data = await request.get_data()
|
|
108
|
+
msg_signature = request.args.get("msg_signature")
|
|
109
|
+
timestamp = request.args.get("timestamp")
|
|
110
|
+
nonce = request.args.get("nonce")
|
|
85
111
|
try:
|
|
86
112
|
xml = self.crypto.decrypt_message(data, msg_signature, timestamp, nonce)
|
|
87
113
|
except InvalidSignatureException:
|
|
@@ -118,14 +144,14 @@ class WecomPlatformAdapter(Platform):
|
|
|
118
144
|
platform_settings: dict,
|
|
119
145
|
event_queue: asyncio.Queue,
|
|
120
146
|
) -> None:
|
|
121
|
-
super().__init__(event_queue)
|
|
122
|
-
self.config = platform_config
|
|
147
|
+
super().__init__(platform_config, event_queue)
|
|
123
148
|
self.settingss = platform_settings
|
|
124
149
|
self.client_self_id = uuid.uuid4().hex[:8]
|
|
125
150
|
self.api_base_url = platform_config.get(
|
|
126
151
|
"api_base_url",
|
|
127
152
|
"https://qyapi.weixin.qq.com/cgi-bin/",
|
|
128
153
|
)
|
|
154
|
+
self.unified_webhook_mode = platform_config.get("unified_webhook_mode", False)
|
|
129
155
|
|
|
130
156
|
if not self.api_base_url:
|
|
131
157
|
self.api_base_url = "https://qyapi.weixin.qq.com/cgi-bin/"
|
|
@@ -232,7 +258,23 @@ class WecomPlatformAdapter(Platform):
|
|
|
232
258
|
)
|
|
233
259
|
except Exception as e:
|
|
234
260
|
logger.error(e)
|
|
235
|
-
|
|
261
|
+
|
|
262
|
+
# 如果启用统一 webhook 模式,则不启动独立服务器
|
|
263
|
+
webhook_uuid = self.config.get("webhook_uuid")
|
|
264
|
+
if self.unified_webhook_mode and webhook_uuid:
|
|
265
|
+
log_webhook_info(f"{self.meta().id}(企业微信)", webhook_uuid)
|
|
266
|
+
# 保持运行状态,等待 shutdown
|
|
267
|
+
await self.server.shutdown_event.wait()
|
|
268
|
+
else:
|
|
269
|
+
await self.server.start_polling()
|
|
270
|
+
|
|
271
|
+
async def webhook_callback(self, request: Any) -> Any:
|
|
272
|
+
"""统一 Webhook 回调入口"""
|
|
273
|
+
# 根据请求方法分发到不同的处理函数
|
|
274
|
+
if request.method == "GET":
|
|
275
|
+
return await self.server.handle_verify(request)
|
|
276
|
+
else:
|
|
277
|
+
return await self.server.handle_callback(request)
|
|
236
278
|
|
|
237
279
|
async def convert_message(self, msg: BaseMessage) -> AstrBotMessage | None:
|
|
238
280
|
abm = AstrBotMessage()
|
|
@@ -22,6 +22,7 @@ from astrbot.api.platform import (
|
|
|
22
22
|
PlatformMetadata,
|
|
23
23
|
)
|
|
24
24
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
25
|
+
from astrbot.core.utils.webhook_utils import log_webhook_info
|
|
25
26
|
|
|
26
27
|
from ...register import register_platform_adapter
|
|
27
28
|
from .wecomai_api import (
|
|
@@ -103,9 +104,7 @@ class WecomAIBotAdapter(Platform):
|
|
|
103
104
|
platform_settings: dict,
|
|
104
105
|
event_queue: asyncio.Queue,
|
|
105
106
|
) -> None:
|
|
106
|
-
super().__init__(event_queue)
|
|
107
|
-
|
|
108
|
-
self.config = platform_config
|
|
107
|
+
super().__init__(platform_config, event_queue)
|
|
109
108
|
self.settings = platform_settings
|
|
110
109
|
|
|
111
110
|
# 初始化配置参数
|
|
@@ -122,6 +121,7 @@ class WecomAIBotAdapter(Platform):
|
|
|
122
121
|
"wecomaibot_friend_message_welcome_text",
|
|
123
122
|
"",
|
|
124
123
|
)
|
|
124
|
+
self.unified_webhook_mode = self.config.get("unified_webhook_mode", False)
|
|
125
125
|
|
|
126
126
|
# 平台元数据
|
|
127
127
|
self.metadata = PlatformMetadata(
|
|
@@ -425,17 +425,34 @@ class WecomAIBotAdapter(Platform):
|
|
|
425
425
|
|
|
426
426
|
def run(self) -> Awaitable[Any]:
|
|
427
427
|
"""运行适配器,同时启动HTTP服务器和队列监听器"""
|
|
428
|
-
logger.info("启动企业微信智能机器人适配器,监听 %s:%d", self.host, self.port)
|
|
429
428
|
|
|
430
429
|
async def run_both():
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
self.
|
|
435
|
-
|
|
430
|
+
# 如果启用统一 webhook 模式,则不启动独立服务器
|
|
431
|
+
webhook_uuid = self.config.get("webhook_uuid")
|
|
432
|
+
if self.unified_webhook_mode and webhook_uuid:
|
|
433
|
+
log_webhook_info(f"{self.meta().id}(企业微信智能机器人)", webhook_uuid)
|
|
434
|
+
# 只运行队列监听器
|
|
435
|
+
await self.queue_listener.run()
|
|
436
|
+
else:
|
|
437
|
+
logger.info(
|
|
438
|
+
"启动企业微信智能机器人适配器,监听 %s:%d", self.host, self.port
|
|
439
|
+
)
|
|
440
|
+
# 同时运行HTTP服务器和队列监听器
|
|
441
|
+
await asyncio.gather(
|
|
442
|
+
self.server.start_server(),
|
|
443
|
+
self.queue_listener.run(),
|
|
444
|
+
)
|
|
436
445
|
|
|
437
446
|
return run_both()
|
|
438
447
|
|
|
448
|
+
async def webhook_callback(self, request: Any) -> Any:
|
|
449
|
+
"""统一 Webhook 回调入口"""
|
|
450
|
+
# 根据请求方法分发到不同的处理函数
|
|
451
|
+
if request.method == "GET":
|
|
452
|
+
return await self.server.handle_verify(request)
|
|
453
|
+
else:
|
|
454
|
+
return await self.server.handle_callback(request)
|
|
455
|
+
|
|
439
456
|
async def terminate(self):
|
|
440
457
|
"""终止适配器"""
|
|
441
458
|
logger.info("企业微信智能机器人适配器正在关闭...")
|
|
@@ -59,8 +59,19 @@ class WecomAIBotServer:
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
async def verify_url(self):
|
|
62
|
-
"""
|
|
63
|
-
|
|
62
|
+
"""内部服务器的 GET 验证入口"""
|
|
63
|
+
return await self.handle_verify(quart.request)
|
|
64
|
+
|
|
65
|
+
async def handle_verify(self, request):
|
|
66
|
+
"""处理 URL 验证请求,可被统一 webhook 入口复用
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
request: Quart 请求对象
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
验证响应元组 (content, status_code, headers)
|
|
73
|
+
"""
|
|
74
|
+
args = request.args
|
|
64
75
|
msg_signature = args.get("msg_signature")
|
|
65
76
|
timestamp = args.get("timestamp")
|
|
66
77
|
nonce = args.get("nonce")
|
|
@@ -81,8 +92,19 @@ class WecomAIBotServer:
|
|
|
81
92
|
return result, 200, {"Content-Type": "text/plain"}
|
|
82
93
|
|
|
83
94
|
async def handle_message(self):
|
|
84
|
-
"""
|
|
85
|
-
|
|
95
|
+
"""内部服务器的 POST 消息回调入口"""
|
|
96
|
+
return await self.handle_callback(quart.request)
|
|
97
|
+
|
|
98
|
+
async def handle_callback(self, request):
|
|
99
|
+
"""处理消息回调,可被统一 webhook 入口复用
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
request: Quart 请求对象
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
响应元组 (content, status_code, headers)
|
|
106
|
+
"""
|
|
107
|
+
args = request.args
|
|
86
108
|
msg_signature = args.get("msg_signature")
|
|
87
109
|
timestamp = args.get("timestamp")
|
|
88
110
|
nonce = args.get("nonce")
|
|
@@ -102,7 +124,7 @@ class WecomAIBotServer:
|
|
|
102
124
|
|
|
103
125
|
try:
|
|
104
126
|
# 获取请求体
|
|
105
|
-
post_data = await
|
|
127
|
+
post_data = await request.get_data()
|
|
106
128
|
|
|
107
129
|
# 确保 post_data 是 bytes 类型
|
|
108
130
|
if isinstance(post_data, str):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import sys
|
|
3
3
|
import uuid
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
import quart
|
|
6
7
|
from requests import Response
|
|
@@ -22,6 +23,7 @@ from astrbot.api.platform import (
|
|
|
22
23
|
)
|
|
23
24
|
from astrbot.core import logger
|
|
24
25
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
26
|
+
from astrbot.core.utils.webhook_utils import log_webhook_info
|
|
25
27
|
|
|
26
28
|
from .weixin_offacc_event import WeixinOfficialAccountPlatformEvent
|
|
27
29
|
|
|
@@ -31,7 +33,7 @@ else:
|
|
|
31
33
|
from typing_extensions import override
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
class
|
|
36
|
+
class WeixinOfficialAccountServer:
|
|
35
37
|
def __init__(self, event_queue: asyncio.Queue, config: dict):
|
|
36
38
|
self.server = quart.Quart(__name__)
|
|
37
39
|
self.port = int(config.get("port"))
|
|
@@ -57,9 +59,21 @@ class WecomServer:
|
|
|
57
59
|
self.shutdown_event = asyncio.Event()
|
|
58
60
|
|
|
59
61
|
async def verify(self):
|
|
60
|
-
|
|
62
|
+
"""内部服务器的 GET 验证入口"""
|
|
63
|
+
return await self.handle_verify(quart.request)
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
async def handle_verify(self, request) -> str:
|
|
66
|
+
"""处理验证请求,可被统一 webhook 入口复用
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
request: Quart 请求对象
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
验证响应
|
|
73
|
+
"""
|
|
74
|
+
logger.info(f"验证请求有效性: {request.args}")
|
|
75
|
+
|
|
76
|
+
args = request.args
|
|
63
77
|
if not args.get("signature", None):
|
|
64
78
|
logger.error("未知的响应,请检查回调地址是否填写正确。")
|
|
65
79
|
return "err"
|
|
@@ -77,10 +91,22 @@ class WecomServer:
|
|
|
77
91
|
return "err"
|
|
78
92
|
|
|
79
93
|
async def callback_command(self):
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
"""内部服务器的 POST 回调入口"""
|
|
95
|
+
return await self.handle_callback(quart.request)
|
|
96
|
+
|
|
97
|
+
async def handle_callback(self, request) -> str:
|
|
98
|
+
"""处理回调请求,可被统一 webhook 入口复用
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
request: Quart 请求对象
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
响应内容
|
|
105
|
+
"""
|
|
106
|
+
data = await request.get_data()
|
|
107
|
+
msg_signature = request.args.get("msg_signature")
|
|
108
|
+
timestamp = request.args.get("timestamp")
|
|
109
|
+
nonce = request.args.get("nonce")
|
|
84
110
|
try:
|
|
85
111
|
xml = self.crypto.decrypt_message(data, msg_signature, timestamp, nonce)
|
|
86
112
|
except InvalidSignatureException:
|
|
@@ -123,8 +149,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
123
149
|
platform_settings: dict,
|
|
124
150
|
event_queue: asyncio.Queue,
|
|
125
151
|
) -> None:
|
|
126
|
-
super().__init__(event_queue)
|
|
127
|
-
self.config = platform_config
|
|
152
|
+
super().__init__(platform_config, event_queue)
|
|
128
153
|
self.settingss = platform_settings
|
|
129
154
|
self.client_self_id = uuid.uuid4().hex[:8]
|
|
130
155
|
self.api_base_url = platform_config.get(
|
|
@@ -132,6 +157,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
132
157
|
"https://api.weixin.qq.com/cgi-bin/",
|
|
133
158
|
)
|
|
134
159
|
self.active_send_mode = self.config.get("active_send_mode", False)
|
|
160
|
+
self.unified_webhook_mode = platform_config.get("unified_webhook_mode", False)
|
|
135
161
|
|
|
136
162
|
if not self.api_base_url:
|
|
137
163
|
self.api_base_url = "https://api.weixin.qq.com/cgi-bin/"
|
|
@@ -143,7 +169,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
143
169
|
if not self.api_base_url.endswith("/"):
|
|
144
170
|
self.api_base_url += "/"
|
|
145
171
|
|
|
146
|
-
self.server =
|
|
172
|
+
self.server = WeixinOfficialAccountServer(self._event_queue, self.config)
|
|
147
173
|
|
|
148
174
|
self.client = WeChatClient(
|
|
149
175
|
self.config["appid"].strip(),
|
|
@@ -202,7 +228,22 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
202
228
|
|
|
203
229
|
@override
|
|
204
230
|
async def run(self):
|
|
205
|
-
|
|
231
|
+
# 如果启用统一 webhook 模式,则不启动独立服务器
|
|
232
|
+
webhook_uuid = self.config.get("webhook_uuid")
|
|
233
|
+
if self.unified_webhook_mode and webhook_uuid:
|
|
234
|
+
log_webhook_info(f"{self.meta().id}(微信公众平台)", webhook_uuid)
|
|
235
|
+
# 保持运行状态,等待 shutdown
|
|
236
|
+
await self.server.shutdown_event.wait()
|
|
237
|
+
else:
|
|
238
|
+
await self.server.start_polling()
|
|
239
|
+
|
|
240
|
+
async def webhook_callback(self, request: Any) -> Any:
|
|
241
|
+
"""统一 Webhook 回调入口"""
|
|
242
|
+
# 根据请求方法分发到不同的处理函数
|
|
243
|
+
if request.method == "GET":
|
|
244
|
+
return await self.server.handle_verify(request)
|
|
245
|
+
else:
|
|
246
|
+
return await self.server.handle_callback(request)
|
|
206
247
|
|
|
207
248
|
async def convert_message(
|
|
208
249
|
self,
|
|
@@ -10,12 +10,12 @@ class PlatformMessageHistoryManager:
|
|
|
10
10
|
self,
|
|
11
11
|
platform_id: str,
|
|
12
12
|
user_id: str,
|
|
13
|
-
content:
|
|
13
|
+
content: dict, # TODO: parse from message chain
|
|
14
14
|
sender_id: str | None = None,
|
|
15
15
|
sender_name: str | None = None,
|
|
16
|
-
):
|
|
16
|
+
) -> PlatformMessageHistory:
|
|
17
17
|
"""Insert a new platform message history record."""
|
|
18
|
-
await self.db.insert_platform_message_history(
|
|
18
|
+
return await self.db.insert_platform_message_history(
|
|
19
19
|
platform_id=platform_id,
|
|
20
20
|
user_id=user_id,
|
|
21
21
|
content=content,
|
|
@@ -6,7 +6,10 @@ from openai import NOT_GIVEN, AsyncOpenAI
|
|
|
6
6
|
from astrbot.core import logger
|
|
7
7
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
8
8
|
from astrbot.core.utils.io import download_file
|
|
9
|
-
from astrbot.core.utils.tencent_record_helper import
|
|
9
|
+
from astrbot.core.utils.tencent_record_helper import (
|
|
10
|
+
convert_to_pcm_wav,
|
|
11
|
+
tencent_silk_to_wav,
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
from ..entities import ProviderType
|
|
12
15
|
from ..provider import STTProvider
|
|
@@ -35,18 +38,28 @@ class ProviderOpenAIWhisperAPI(STTProvider):
|
|
|
35
38
|
|
|
36
39
|
self.set_model(provider_config.get("model"))
|
|
37
40
|
|
|
38
|
-
async def
|
|
41
|
+
async def _get_audio_format(self, file_path):
|
|
42
|
+
# 定义要检测的头部字节
|
|
39
43
|
silk_header = b"SILK"
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
amr_header = b"#!AMR"
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with open(file_path, "rb") as f:
|
|
48
|
+
file_header = f.read(8)
|
|
49
|
+
except FileNotFoundError:
|
|
50
|
+
return None
|
|
42
51
|
|
|
43
52
|
if silk_header in file_header:
|
|
44
|
-
return
|
|
45
|
-
|
|
53
|
+
return "silk"
|
|
54
|
+
|
|
55
|
+
if amr_header in file_header:
|
|
56
|
+
return "amr"
|
|
57
|
+
return None
|
|
46
58
|
|
|
47
59
|
async def get_text(self, audio_url: str) -> str:
|
|
48
60
|
"""Only supports mp3, mp4, mpeg, m4a, wav, webm"""
|
|
49
61
|
is_tencent = False
|
|
62
|
+
output_path = None
|
|
50
63
|
|
|
51
64
|
if audio_url.startswith("http"):
|
|
52
65
|
if "multimedia.nt.qq.com.cn" in audio_url:
|
|
@@ -62,16 +75,35 @@ class ProviderOpenAIWhisperAPI(STTProvider):
|
|
|
62
75
|
raise FileNotFoundError(f"文件不存在: {audio_url}")
|
|
63
76
|
|
|
64
77
|
if audio_url.endswith(".amr") or audio_url.endswith(".silk") or is_tencent:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
file_format = await self._get_audio_format(audio_url)
|
|
79
|
+
|
|
80
|
+
# 判断是否需要转换
|
|
81
|
+
if file_format in ["silk", "amr"]:
|
|
68
82
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
69
83
|
output_path = os.path.join(temp_dir, str(uuid.uuid4()) + ".wav")
|
|
70
|
-
|
|
84
|
+
|
|
85
|
+
if file_format == "silk":
|
|
86
|
+
logger.info(
|
|
87
|
+
"Converting silk file to wav using tencent_silk_to_wav..."
|
|
88
|
+
)
|
|
89
|
+
await tencent_silk_to_wav(audio_url, output_path)
|
|
90
|
+
elif file_format == "amr":
|
|
91
|
+
logger.info(
|
|
92
|
+
"Converting amr file to wav using convert_to_pcm_wav..."
|
|
93
|
+
)
|
|
94
|
+
await convert_to_pcm_wav(audio_url, output_path)
|
|
95
|
+
|
|
71
96
|
audio_url = output_path
|
|
72
97
|
|
|
73
98
|
result = await self.client.audio.transcriptions.create(
|
|
74
99
|
model=self.model_name,
|
|
75
|
-
file=open(audio_url, "rb"),
|
|
100
|
+
file=("audio.wav", open(audio_url, "rb")),
|
|
76
101
|
)
|
|
102
|
+
|
|
103
|
+
# remove temp file
|
|
104
|
+
if output_path and os.path.exists(output_path):
|
|
105
|
+
try:
|
|
106
|
+
os.remove(audio_url)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Failed to remove temp file {audio_url}: {e}")
|
|
77
109
|
return result.text
|
|
@@ -36,7 +36,7 @@ async def wav_to_tencent_silk(wav_path: str, output_path: str) -> int:
|
|
|
36
36
|
import pilk
|
|
37
37
|
except (ImportError, ModuleNotFoundError) as _:
|
|
38
38
|
raise Exception(
|
|
39
|
-
"pilk
|
|
39
|
+
"pilk 模块未安装,请前往管理面板->平台日志->安装pip库 安装 pilk 这个库",
|
|
40
40
|
)
|
|
41
41
|
# with wave.open(wav_path, 'rb') as wav:
|
|
42
42
|
# wav_data = wav.readframes(wav.getnframes())
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from astrbot.core import astrbot_config, logger
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _get_callback_api_base() -> str:
|
|
5
|
+
try:
|
|
6
|
+
return astrbot_config.get("callback_api_base", "").rstrip("/")
|
|
7
|
+
except Exception as e:
|
|
8
|
+
logger.error(f"获取 callback_api_base 失败: {e!s}")
|
|
9
|
+
return ""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_dashboard_port() -> int:
|
|
13
|
+
try:
|
|
14
|
+
return astrbot_config.get("dashboard", {}).get("port", 6185)
|
|
15
|
+
except Exception as e:
|
|
16
|
+
logger.error(f"获取 dashboard 端口失败: {e!s}")
|
|
17
|
+
return 6185
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def log_webhook_info(platform_name: str, webhook_uuid: str):
|
|
21
|
+
"""打印美观的 webhook 信息日志
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
platform_name: 平台名称
|
|
25
|
+
webhook_uuid: webhook 的 UUID
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
callback_base = _get_callback_api_base()
|
|
29
|
+
|
|
30
|
+
if not callback_base:
|
|
31
|
+
callback_base = "http(s)://<your-astrbot-domain>"
|
|
32
|
+
|
|
33
|
+
if not callback_base.startswith("http"):
|
|
34
|
+
callback_base = f"http(s)://{callback_base}"
|
|
35
|
+
|
|
36
|
+
callback_base = callback_base.rstrip("/")
|
|
37
|
+
webhook_url = f"{callback_base}/api/platform/webhook/{webhook_uuid}"
|
|
38
|
+
|
|
39
|
+
display_log = (
|
|
40
|
+
"\n====================\n"
|
|
41
|
+
f"🔗 机器人平台 {platform_name} 已启用统一 Webhook 模式\n"
|
|
42
|
+
f"📍 Webhook 回调地址: \n"
|
|
43
|
+
f" ➜ http://<your-ip>:{_get_dashboard_port()}/api/platform/webhook/{webhook_uuid}\n"
|
|
44
|
+
f" ➜ {webhook_url}\n"
|
|
45
|
+
"====================\n"
|
|
46
|
+
)
|
|
47
|
+
logger.info(display_log)
|
|
@@ -6,6 +6,7 @@ from .file import FileRoute
|
|
|
6
6
|
from .knowledge_base import KnowledgeBaseRoute
|
|
7
7
|
from .log import LogRoute
|
|
8
8
|
from .persona import PersonaRoute
|
|
9
|
+
from .platform import PlatformRoute
|
|
9
10
|
from .plugin import PluginRoute
|
|
10
11
|
from .session_management import SessionManagementRoute
|
|
11
12
|
from .stat import StatRoute
|
|
@@ -22,6 +23,7 @@ __all__ = [
|
|
|
22
23
|
"KnowledgeBaseRoute",
|
|
23
24
|
"LogRoute",
|
|
24
25
|
"PersonaRoute",
|
|
26
|
+
"PlatformRoute",
|
|
25
27
|
"PluginRoute",
|
|
26
28
|
"SessionManagementRoute",
|
|
27
29
|
"StatRoute",
|