AstrBot 4.3.2__py3-none-any.whl → 4.3.5__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/core/agent/mcp_client.py +18 -4
- astrbot/core/astr_agent_context.py +1 -0
- astrbot/core/config/default.py +66 -9
- astrbot/core/db/sqlite.py +7 -0
- astrbot/core/pipeline/context_utils.py +1 -0
- astrbot/core/pipeline/process_stage/method/llm_request.py +32 -14
- astrbot/core/pipeline/result_decorate/stage.py +44 -45
- astrbot/core/pipeline/scheduler.py +1 -1
- astrbot/core/platform/manager.py +4 -0
- astrbot/core/platform/sources/satori/satori_event.py +23 -1
- astrbot/core/platform/sources/webchat/webchat_adapter.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +289 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +17 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +20 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +445 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +378 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +149 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +148 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +166 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +199 -0
- astrbot/core/provider/provider.py +2 -1
- astrbot/core/provider/sources/anthropic_source.py +13 -7
- astrbot/core/provider/sources/dashscope_tts.py +120 -12
- astrbot/core/provider/sources/gemini_source.py +21 -17
- astrbot/core/provider/sources/openai_source.py +1 -1
- astrbot/dashboard/routes/session_management.py +6 -6
- astrbot/dashboard/routes/tools.py +14 -0
- astrbot/dashboard/routes/update.py +8 -5
- {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/METADATA +64 -44
- {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/RECORD +33 -24
- {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/WHEEL +0 -0
- {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/entry_points.txt +0 -0
- {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/licenses/LICENSE +0 -0
astrbot/core/agent/mcp_client.py
CHANGED
|
@@ -40,8 +40,15 @@ async def _quick_test_mcp_connection(config: dict) -> tuple[bool, str]:
|
|
|
40
40
|
timeout = cfg.get("timeout", 10)
|
|
41
41
|
|
|
42
42
|
try:
|
|
43
|
+
if "transport" in cfg:
|
|
44
|
+
transport_type = cfg["transport"]
|
|
45
|
+
elif "type" in cfg:
|
|
46
|
+
transport_type = cfg["type"]
|
|
47
|
+
else:
|
|
48
|
+
raise Exception("MCP 连接配置缺少 transport 或 type 字段")
|
|
49
|
+
|
|
43
50
|
async with aiohttp.ClientSession() as session:
|
|
44
|
-
if
|
|
51
|
+
if transport_type == "streamable_http":
|
|
45
52
|
test_payload = {
|
|
46
53
|
"jsonrpc": "2.0",
|
|
47
54
|
"method": "initialize",
|
|
@@ -121,7 +128,14 @@ class MCPClient:
|
|
|
121
128
|
if not success:
|
|
122
129
|
raise Exception(error_msg)
|
|
123
130
|
|
|
124
|
-
if
|
|
131
|
+
if "transport" in cfg:
|
|
132
|
+
transport_type = cfg["transport"]
|
|
133
|
+
elif "type" in cfg:
|
|
134
|
+
transport_type = cfg["type"]
|
|
135
|
+
else:
|
|
136
|
+
raise Exception("MCP 连接配置缺少 transport 或 type 字段")
|
|
137
|
+
|
|
138
|
+
if transport_type != "streamable_http":
|
|
125
139
|
# SSE transport method
|
|
126
140
|
self._streams_context = sse_client(
|
|
127
141
|
url=cfg["url"],
|
|
@@ -134,7 +148,7 @@ class MCPClient:
|
|
|
134
148
|
)
|
|
135
149
|
|
|
136
150
|
# Create a new client session
|
|
137
|
-
read_timeout = timedelta(seconds=cfg.get("session_read_timeout",
|
|
151
|
+
read_timeout = timedelta(seconds=cfg.get("session_read_timeout", 60))
|
|
138
152
|
self.session = await self.exit_stack.enter_async_context(
|
|
139
153
|
mcp.ClientSession(
|
|
140
154
|
*streams,
|
|
@@ -159,7 +173,7 @@ class MCPClient:
|
|
|
159
173
|
)
|
|
160
174
|
|
|
161
175
|
# Create a new client session
|
|
162
|
-
read_timeout = timedelta(seconds=cfg.get("session_read_timeout",
|
|
176
|
+
read_timeout = timedelta(seconds=cfg.get("session_read_timeout", 60))
|
|
163
177
|
self.session = await self.exit_stack.enter_async_context(
|
|
164
178
|
mcp.ClientSession(
|
|
165
179
|
read_stream=read_s,
|
astrbot/core/config/default.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
|
|
7
7
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
8
8
|
|
|
9
|
-
VERSION = "4.3.
|
|
9
|
+
VERSION = "4.3.5"
|
|
10
10
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
11
11
|
|
|
12
12
|
# 默认配置
|
|
@@ -57,6 +57,7 @@ DEFAULT_CONFIG = {
|
|
|
57
57
|
"web_search": False,
|
|
58
58
|
"websearch_provider": "default",
|
|
59
59
|
"websearch_tavily_key": [],
|
|
60
|
+
"websearch_baidu_app_builder_key": "",
|
|
60
61
|
"web_search_link": False,
|
|
61
62
|
"display_reasoning_text": False,
|
|
62
63
|
"identifier": False,
|
|
@@ -71,6 +72,7 @@ DEFAULT_CONFIG = {
|
|
|
71
72
|
"show_tool_use_status": False,
|
|
72
73
|
"streaming_segmented": False,
|
|
73
74
|
"max_agent_step": 30,
|
|
75
|
+
"tool_call_timeout": 60,
|
|
74
76
|
},
|
|
75
77
|
"provider_stt_settings": {
|
|
76
78
|
"enable": False,
|
|
@@ -207,6 +209,18 @@ CONFIG_METADATA_2 = {
|
|
|
207
209
|
"callback_server_host": "0.0.0.0",
|
|
208
210
|
"port": 6195,
|
|
209
211
|
},
|
|
212
|
+
"企业微信智能机器人": {
|
|
213
|
+
"id": "wecom_ai_bot",
|
|
214
|
+
"type": "wecom_ai_bot",
|
|
215
|
+
"enable": True,
|
|
216
|
+
"wecomaibot_init_respond_text": "💭 思考中...",
|
|
217
|
+
"wecomaibot_friend_message_welcome_text": "",
|
|
218
|
+
"wecom_ai_bot_name": "",
|
|
219
|
+
"token": "",
|
|
220
|
+
"encoding_aes_key": "",
|
|
221
|
+
"callback_server_host": "0.0.0.0",
|
|
222
|
+
"port": 6198,
|
|
223
|
+
},
|
|
210
224
|
"飞书(Lark)": {
|
|
211
225
|
"id": "lark",
|
|
212
226
|
"type": "lark",
|
|
@@ -447,10 +461,25 @@ CONFIG_METADATA_2 = {
|
|
|
447
461
|
"type": "string",
|
|
448
462
|
"hint": "aiocqhttp 适配器的反向 Websocket Token。未设置则不启用 Token 验证。",
|
|
449
463
|
},
|
|
464
|
+
"wecom_ai_bot_name": {
|
|
465
|
+
"description": "企业微信智能机器人的名字",
|
|
466
|
+
"type": "string",
|
|
467
|
+
"hint": "请务必填写正确,否则无法使用一些指令。",
|
|
468
|
+
},
|
|
469
|
+
"wecomaibot_init_respond_text": {
|
|
470
|
+
"description": "企业微信智能机器人初始响应文本",
|
|
471
|
+
"type": "string",
|
|
472
|
+
"hint": "当机器人收到消息时,首先回复的文本内容。留空则使用默认值。",
|
|
473
|
+
},
|
|
474
|
+
"wecomaibot_friend_message_welcome_text": {
|
|
475
|
+
"description": "企业微信智能机器人私聊欢迎语",
|
|
476
|
+
"type": "string",
|
|
477
|
+
"hint": "当用户当天进入智能机器人单聊会话,回复欢迎语,留空则不回复。",
|
|
478
|
+
},
|
|
450
479
|
"lark_bot_name": {
|
|
451
480
|
"description": "飞书机器人的名字",
|
|
452
481
|
"type": "string",
|
|
453
|
-
"hint": "
|
|
482
|
+
"hint": "请务必填写正确,否则 @ 机器人将无法唤醒,只能通过前缀唤醒。",
|
|
454
483
|
},
|
|
455
484
|
"discord_token": {
|
|
456
485
|
"description": "Discord Bot Token",
|
|
@@ -775,7 +804,7 @@ CONFIG_METADATA_2 = {
|
|
|
775
804
|
"timeout": 120,
|
|
776
805
|
"model_config": {"model": "deepseek-chat", "temperature": 0.4},
|
|
777
806
|
"custom_extra_body": {},
|
|
778
|
-
"modalities": ["text", "
|
|
807
|
+
"modalities": ["text", "tool_use"],
|
|
779
808
|
},
|
|
780
809
|
"302.AI": {
|
|
781
810
|
"id": "302ai",
|
|
@@ -821,6 +850,21 @@ CONFIG_METADATA_2 = {
|
|
|
821
850
|
},
|
|
822
851
|
"custom_extra_body": {},
|
|
823
852
|
},
|
|
853
|
+
"小马算力": {
|
|
854
|
+
"id": "tokenpony",
|
|
855
|
+
"provider": "tokenpony",
|
|
856
|
+
"type": "openai_chat_completion",
|
|
857
|
+
"provider_type": "chat_completion",
|
|
858
|
+
"enable": True,
|
|
859
|
+
"key": [],
|
|
860
|
+
"api_base": "https://api.tokenpony.cn/v1",
|
|
861
|
+
"timeout": 120,
|
|
862
|
+
"model_config": {
|
|
863
|
+
"model": "kimi-k2-instruct-0905",
|
|
864
|
+
"temperature": 0.7,
|
|
865
|
+
},
|
|
866
|
+
"custom_extra_body": {},
|
|
867
|
+
},
|
|
824
868
|
"优云智算": {
|
|
825
869
|
"id": "compshare",
|
|
826
870
|
"provider": "compshare",
|
|
@@ -1041,6 +1085,7 @@ CONFIG_METADATA_2 = {
|
|
|
1041
1085
|
"timeout": "20",
|
|
1042
1086
|
},
|
|
1043
1087
|
"阿里云百炼 TTS(API)": {
|
|
1088
|
+
"hint": "API Key 从 https://bailian.console.aliyun.com/?tab=model#/api-key 获取。模型和音色的选择文档请参考: 阿里云百炼语音合成音色名称。具体可参考 https://help.aliyun.com/zh/model-studio/speech-synthesis-and-speech-recognition",
|
|
1044
1089
|
"id": "dashscope_tts",
|
|
1045
1090
|
"provider": "dashscope",
|
|
1046
1091
|
"type": "dashscope_tts",
|
|
@@ -1420,11 +1465,7 @@ CONFIG_METADATA_2 = {
|
|
|
1420
1465
|
"description": "服务订阅密钥",
|
|
1421
1466
|
"hint": "Azure_TTS 服务的订阅密钥(注意不是令牌)",
|
|
1422
1467
|
},
|
|
1423
|
-
"dashscope_tts_voice": {
|
|
1424
|
-
"description": "语音合成模型",
|
|
1425
|
-
"type": "string",
|
|
1426
|
-
"hint": "阿里云百炼语音合成模型名称。具体可参考 https://help.aliyun.com/zh/model-studio/developer-reference/cosyvoice-python-api 等内容",
|
|
1427
|
-
},
|
|
1468
|
+
"dashscope_tts_voice": {"description": "音色", "type": "string"},
|
|
1428
1469
|
"gm_resp_image_modal": {
|
|
1429
1470
|
"description": "启用图片模态",
|
|
1430
1471
|
"type": "bool",
|
|
@@ -1833,6 +1874,10 @@ CONFIG_METADATA_2 = {
|
|
|
1833
1874
|
"description": "工具调用轮数上限",
|
|
1834
1875
|
"type": "int",
|
|
1835
1876
|
},
|
|
1877
|
+
"tool_call_timeout": {
|
|
1878
|
+
"description": "工具调用超时时间(秒)",
|
|
1879
|
+
"type": "int",
|
|
1880
|
+
},
|
|
1836
1881
|
},
|
|
1837
1882
|
},
|
|
1838
1883
|
"provider_stt_settings": {
|
|
@@ -2051,7 +2096,7 @@ CONFIG_METADATA_3 = {
|
|
|
2051
2096
|
"provider_settings.websearch_provider": {
|
|
2052
2097
|
"description": "网页搜索提供商",
|
|
2053
2098
|
"type": "string",
|
|
2054
|
-
"options": ["default", "tavily"],
|
|
2099
|
+
"options": ["default", "tavily", "baidu_ai_search"],
|
|
2055
2100
|
},
|
|
2056
2101
|
"provider_settings.websearch_tavily_key": {
|
|
2057
2102
|
"description": "Tavily API Key",
|
|
@@ -2062,6 +2107,14 @@ CONFIG_METADATA_3 = {
|
|
|
2062
2107
|
"provider_settings.websearch_provider": "tavily",
|
|
2063
2108
|
},
|
|
2064
2109
|
},
|
|
2110
|
+
"provider_settings.websearch_baidu_app_builder_key": {
|
|
2111
|
+
"description": "百度千帆智能云 APP Builder API Key",
|
|
2112
|
+
"type": "string",
|
|
2113
|
+
"hint": "参考:https://console.bce.baidu.com/iam/#/iam/apikey/list",
|
|
2114
|
+
"condition": {
|
|
2115
|
+
"provider_settings.websearch_provider": "baidu_ai_search",
|
|
2116
|
+
},
|
|
2117
|
+
},
|
|
2065
2118
|
"provider_settings.web_search_link": {
|
|
2066
2119
|
"description": "显示来源引用",
|
|
2067
2120
|
"type": "bool",
|
|
@@ -2097,6 +2150,10 @@ CONFIG_METADATA_3 = {
|
|
|
2097
2150
|
"description": "工具调用轮数上限",
|
|
2098
2151
|
"type": "int",
|
|
2099
2152
|
},
|
|
2153
|
+
"provider_settings.tool_call_timeout": {
|
|
2154
|
+
"description": "工具调用超时时间(秒)",
|
|
2155
|
+
"type": "int",
|
|
2156
|
+
},
|
|
2100
2157
|
"provider_settings.streaming_response": {
|
|
2101
2158
|
"description": "流式回复",
|
|
2102
2159
|
"type": "bool",
|
astrbot/core/db/sqlite.py
CHANGED
|
@@ -32,6 +32,12 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
32
32
|
"""Initialize the database by creating tables if they do not exist."""
|
|
33
33
|
async with self.engine.begin() as conn:
|
|
34
34
|
await conn.run_sync(SQLModel.metadata.create_all)
|
|
35
|
+
await conn.execute(text("PRAGMA journal_mode=WAL"))
|
|
36
|
+
await conn.execute(text("PRAGMA synchronous=NORMAL"))
|
|
37
|
+
await conn.execute(text("PRAGMA cache_size=20000"))
|
|
38
|
+
await conn.execute(text("PRAGMA temp_store=MEMORY"))
|
|
39
|
+
await conn.execute(text("PRAGMA mmap_size=134217728"))
|
|
40
|
+
await conn.execute(text("PRAGMA optimize"))
|
|
35
41
|
await conn.commit()
|
|
36
42
|
|
|
37
43
|
# ====
|
|
@@ -160,6 +166,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
160
166
|
col(ConversationV2.title).ilike(f"%{search_query}%"),
|
|
161
167
|
col(ConversationV2.content).ilike(f"%{search_query}%"),
|
|
162
168
|
col(ConversationV2.user_id).ilike(f"%{search_query}%"),
|
|
169
|
+
col(ConversationV2.conversation_id).ilike(f"%{search_query}%"),
|
|
163
170
|
)
|
|
164
171
|
)
|
|
165
172
|
if "message_types" in kwargs and len(kwargs["message_types"]) > 0:
|
|
@@ -6,6 +6,7 @@ import asyncio
|
|
|
6
6
|
import copy
|
|
7
7
|
import json
|
|
8
8
|
import traceback
|
|
9
|
+
from datetime import timedelta
|
|
9
10
|
from typing import AsyncGenerator, Union
|
|
10
11
|
from astrbot.core.conversation_mgr import Conversation
|
|
11
12
|
from astrbot.core import logger
|
|
@@ -185,21 +186,33 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
|
|
|
185
186
|
handler=awaitable,
|
|
186
187
|
**tool_args,
|
|
187
188
|
)
|
|
188
|
-
async for resp in wrapper:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
# async for resp in wrapper:
|
|
190
|
+
while True:
|
|
191
|
+
try:
|
|
192
|
+
resp = await asyncio.wait_for(
|
|
193
|
+
anext(wrapper),
|
|
194
|
+
timeout=run_context.context.tool_call_timeout,
|
|
195
|
+
)
|
|
196
|
+
if resp is not None:
|
|
197
|
+
if isinstance(resp, mcp.types.CallToolResult):
|
|
198
|
+
yield resp
|
|
199
|
+
else:
|
|
200
|
+
text_content = mcp.types.TextContent(
|
|
201
|
+
type="text",
|
|
202
|
+
text=str(resp),
|
|
203
|
+
)
|
|
204
|
+
yield mcp.types.CallToolResult(content=[text_content])
|
|
192
205
|
else:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
206
|
+
# NOTE: Tool 在这里直接请求发送消息给用户
|
|
207
|
+
# TODO: 是否需要判断 event.get_result() 是否为空?
|
|
208
|
+
# 如果为空,则说明没有发送消息给用户,并且返回值为空,将返回一个特殊的 TextContent,其内容如"工具没有返回内容"
|
|
209
|
+
yield None
|
|
210
|
+
except asyncio.TimeoutError:
|
|
211
|
+
raise Exception(
|
|
212
|
+
f"tool {tool.name} execution timeout after {run_context.context.tool_call_timeout} seconds."
|
|
213
|
+
)
|
|
214
|
+
except StopAsyncIteration:
|
|
215
|
+
break
|
|
203
216
|
|
|
204
217
|
@classmethod
|
|
205
218
|
async def _execute_mcp(
|
|
@@ -217,6 +230,9 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
|
|
|
217
230
|
res = await session.call_tool(
|
|
218
231
|
name=tool.name,
|
|
219
232
|
arguments=tool_args,
|
|
233
|
+
read_timeout_seconds=timedelta(
|
|
234
|
+
seconds=run_context.context.tool_call_timeout
|
|
235
|
+
),
|
|
220
236
|
)
|
|
221
237
|
if not res:
|
|
222
238
|
return
|
|
@@ -307,6 +323,7 @@ class LLMRequestSubStage(Stage):
|
|
|
307
323
|
)
|
|
308
324
|
self.streaming_response: bool = settings["streaming_response"]
|
|
309
325
|
self.max_step: int = settings.get("max_agent_step", 30)
|
|
326
|
+
self.tool_call_timeout: int = settings.get("tool_call_timeout", 60)
|
|
310
327
|
if isinstance(self.max_step, bool): # workaround: #2622
|
|
311
328
|
self.max_step = 30
|
|
312
329
|
self.show_tool_use: bool = settings.get("show_tool_use_status", True)
|
|
@@ -473,6 +490,7 @@ class LLMRequestSubStage(Stage):
|
|
|
473
490
|
first_provider_request=req,
|
|
474
491
|
curr_provider_request=req,
|
|
475
492
|
streaming=self.streaming_response,
|
|
493
|
+
tool_call_timeout=self.tool_call_timeout,
|
|
476
494
|
)
|
|
477
495
|
await agent_runner.reset(
|
|
478
496
|
provider=provider,
|
|
@@ -189,54 +189,54 @@ class ResultDecorateStage(Stage):
|
|
|
189
189
|
logger.warning(
|
|
190
190
|
f"会话 {event.unified_msg_origin} 未配置文本转语音模型。"
|
|
191
191
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
192
|
+
else:
|
|
193
|
+
new_chain = []
|
|
194
|
+
for comp in result.chain:
|
|
195
|
+
if isinstance(comp, Plain) and len(comp.text) > 1:
|
|
196
|
+
try:
|
|
197
|
+
logger.info(f"TTS 请求: {comp.text}")
|
|
198
|
+
audio_path = await tts_provider.get_audio(comp.text)
|
|
199
|
+
logger.info(f"TTS 结果: {audio_path}")
|
|
200
|
+
if not audio_path:
|
|
201
|
+
logger.error(
|
|
202
|
+
f"由于 TTS 音频文件未找到,消息段转语音失败: {comp.text}"
|
|
203
|
+
)
|
|
204
|
+
new_chain.append(comp)
|
|
205
|
+
continue
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
207
|
+
use_file_service = self.ctx.astrbot_config[
|
|
208
|
+
"provider_tts_settings"
|
|
209
|
+
]["use_file_service"]
|
|
210
|
+
callback_api_base = self.ctx.astrbot_config[
|
|
211
|
+
"callback_api_base"
|
|
212
|
+
]
|
|
213
|
+
dual_output = self.ctx.astrbot_config[
|
|
214
|
+
"provider_tts_settings"
|
|
215
|
+
]["dual_output"]
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
url = None
|
|
218
|
+
if use_file_service and callback_api_base:
|
|
219
|
+
token = await file_token_service.register_file(
|
|
220
|
+
audio_path
|
|
221
|
+
)
|
|
222
|
+
url = f"{callback_api_base}/api/file/{token}"
|
|
223
|
+
logger.debug(f"已注册:{url}")
|
|
224
224
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
new_chain.append(
|
|
226
|
+
Record(
|
|
227
|
+
file=url or audio_path,
|
|
228
|
+
url=url or audio_path,
|
|
229
|
+
)
|
|
229
230
|
)
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
if dual_output:
|
|
232
|
+
new_chain.append(comp)
|
|
233
|
+
except Exception:
|
|
234
|
+
logger.error(traceback.format_exc())
|
|
235
|
+
logger.error("TTS 失败,使用文本发送。")
|
|
232
236
|
new_chain.append(comp)
|
|
233
|
-
|
|
234
|
-
logger.error(traceback.format_exc())
|
|
235
|
-
logger.error("TTS 失败,使用文本发送。")
|
|
237
|
+
else:
|
|
236
238
|
new_chain.append(comp)
|
|
237
|
-
|
|
238
|
-
new_chain.append(comp)
|
|
239
|
-
result.chain = new_chain
|
|
239
|
+
result.chain = new_chain
|
|
240
240
|
|
|
241
241
|
# 文本转图片
|
|
242
242
|
elif (
|
|
@@ -279,7 +279,6 @@ class ResultDecorateStage(Stage):
|
|
|
279
279
|
result.chain = [Image.fromFileSystem(url)]
|
|
280
280
|
|
|
281
281
|
# 触发转发消息
|
|
282
|
-
has_forwarded = False
|
|
283
282
|
if event.get_platform_name() == "aiocqhttp":
|
|
284
283
|
word_cnt = 0
|
|
285
284
|
for comp in result.chain:
|
|
@@ -290,9 +289,9 @@ class ResultDecorateStage(Stage):
|
|
|
290
289
|
uin=event.get_self_id(), name="AstrBot", content=[*result.chain]
|
|
291
290
|
)
|
|
292
291
|
result.chain = [node]
|
|
293
|
-
has_forwarded = True
|
|
294
292
|
|
|
295
|
-
|
|
293
|
+
has_plain = any(isinstance(item, Plain) for item in result.chain)
|
|
294
|
+
if has_plain:
|
|
296
295
|
# at 回复
|
|
297
296
|
if (
|
|
298
297
|
self.reply_with_mention
|
|
@@ -74,7 +74,7 @@ class PipelineScheduler:
|
|
|
74
74
|
await self._process_stages(event)
|
|
75
75
|
|
|
76
76
|
# 如果没有发送操作, 则发送一个空消息, 以便于后续的处理
|
|
77
|
-
if event.get_platform_name()
|
|
77
|
+
if event.get_platform_name() in ["webchat", "wecom_ai_bot"]:
|
|
78
78
|
await event.send(None)
|
|
79
79
|
|
|
80
80
|
logger.debug("pipeline 执行完毕。")
|
astrbot/core/platform/manager.py
CHANGED
|
@@ -82,6 +82,10 @@ class PlatformManager:
|
|
|
82
82
|
from .sources.wecom.wecom_adapter import (
|
|
83
83
|
WecomPlatformAdapter, # noqa: F401
|
|
84
84
|
)
|
|
85
|
+
case "wecom_ai_bot":
|
|
86
|
+
from .sources.wecom_ai_bot.wecomai_adapter import (
|
|
87
|
+
WecomAIBotAdapter, # noqa: F401
|
|
88
|
+
)
|
|
85
89
|
case "weixin_official_account":
|
|
86
90
|
from .sources.weixin_official_account.weixin_offacc_adapter import (
|
|
87
91
|
WeixinOfficialAccountPlatformAdapter, # noqa: F401
|
|
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
|
|
|
2
2
|
from astrbot.api import logger
|
|
3
3
|
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
4
4
|
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
|
|
5
|
-
from astrbot.api.message_components import Plain, Image, At, File, Record
|
|
5
|
+
from astrbot.api.message_components import Plain, Image, At, File, Record, Video, Reply
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from .satori_adapter import SatoriPlatformAdapter
|
|
@@ -87,6 +87,17 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
87
87
|
except Exception as e:
|
|
88
88
|
logger.error(f"语音转换为base64失败: {e}")
|
|
89
89
|
|
|
90
|
+
elif isinstance(component, Reply):
|
|
91
|
+
content_parts.append(f'<reply id="{component.id}"/>')
|
|
92
|
+
|
|
93
|
+
elif isinstance(component, Video):
|
|
94
|
+
try:
|
|
95
|
+
video_path_url = await component.convert_to_file_path()
|
|
96
|
+
if video_path_url:
|
|
97
|
+
content_parts.append(f'<video src="{video_path_url}"/>')
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"视频文件转换失败: {e}")
|
|
100
|
+
|
|
90
101
|
content = "".join(content_parts)
|
|
91
102
|
channel_id = session_id
|
|
92
103
|
data = {"channel_id": channel_id, "content": content}
|
|
@@ -166,6 +177,17 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
166
177
|
except Exception as e:
|
|
167
178
|
logger.error(f"语音转换为base64失败: {e}")
|
|
168
179
|
|
|
180
|
+
elif isinstance(component, Reply):
|
|
181
|
+
content_parts.append(f'<reply id="{component.id}"/>')
|
|
182
|
+
|
|
183
|
+
elif isinstance(component, Video):
|
|
184
|
+
try:
|
|
185
|
+
video_path_url = await component.convert_to_file_path()
|
|
186
|
+
if video_path_url:
|
|
187
|
+
content_parts.append(f'<video src="{video_path_url}"/>')
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.error(f"视频文件转换失败: {e}")
|
|
190
|
+
|
|
169
191
|
content = "".join(content_parts)
|
|
170
192
|
channel_id = self.session_id
|
|
171
193
|
data = {"channel_id": channel_id, "content": content}
|