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.
Files changed (33) hide show
  1. astrbot/core/agent/mcp_client.py +18 -4
  2. astrbot/core/astr_agent_context.py +1 -0
  3. astrbot/core/config/default.py +66 -9
  4. astrbot/core/db/sqlite.py +7 -0
  5. astrbot/core/pipeline/context_utils.py +1 -0
  6. astrbot/core/pipeline/process_stage/method/llm_request.py +32 -14
  7. astrbot/core/pipeline/result_decorate/stage.py +44 -45
  8. astrbot/core/pipeline/scheduler.py +1 -1
  9. astrbot/core/platform/manager.py +4 -0
  10. astrbot/core/platform/sources/satori/satori_event.py +23 -1
  11. astrbot/core/platform/sources/webchat/webchat_adapter.py +0 -1
  12. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +289 -0
  13. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +17 -0
  14. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +20 -0
  15. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +445 -0
  16. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +378 -0
  17. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +149 -0
  18. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +148 -0
  19. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +166 -0
  20. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +199 -0
  21. astrbot/core/provider/provider.py +2 -1
  22. astrbot/core/provider/sources/anthropic_source.py +13 -7
  23. astrbot/core/provider/sources/dashscope_tts.py +120 -12
  24. astrbot/core/provider/sources/gemini_source.py +21 -17
  25. astrbot/core/provider/sources/openai_source.py +1 -1
  26. astrbot/dashboard/routes/session_management.py +6 -6
  27. astrbot/dashboard/routes/tools.py +14 -0
  28. astrbot/dashboard/routes/update.py +8 -5
  29. {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/METADATA +64 -44
  30. {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/RECORD +33 -24
  31. {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/WHEEL +0 -0
  32. {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/entry_points.txt +0 -0
  33. {astrbot-4.3.2.dist-info → astrbot-4.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -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 cfg.get("transport") == "streamable_http":
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 cfg.get("transport") != "streamable_http":
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", 20))
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", 20))
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,
@@ -9,3 +9,4 @@ class AstrAgentContext:
9
9
  first_provider_request: ProviderRequest
10
10
  curr_provider_request: ProviderRequest
11
11
  streaming: bool
12
+ tool_call_timeout: int = 60 # Default tool call timeout in seconds
@@ -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.2"
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", "image", "tool_use"],
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:
@@ -97,5 +97,6 @@ async def call_event_hook(
97
97
  logger.info(
98
98
  f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。"
99
99
  )
100
+ return True
100
101
 
101
102
  return event.is_stopped()
@@ -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
- if resp is not None:
190
- if isinstance(resp, mcp.types.CallToolResult):
191
- yield resp
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
- text_content = mcp.types.TextContent(
194
- type="text",
195
- text=str(resp),
196
- )
197
- yield mcp.types.CallToolResult(content=[text_content])
198
- else:
199
- # NOTE: Tool 在这里直接请求发送消息给用户
200
- # TODO: 是否需要判断 event.get_result() 是否为空?
201
- # 如果为空,则说明没有发送消息给用户,并且返回值为空,将返回一个特殊的 TextContent,其内容如"工具没有返回内容"
202
- yield None
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
- return
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
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
- 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"]
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
- 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}")
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
- new_chain.append(
226
- Record(
227
- file=url or audio_path,
228
- url=url or audio_path,
225
+ new_chain.append(
226
+ Record(
227
+ file=url or audio_path,
228
+ url=url or audio_path,
229
+ )
229
230
  )
230
- )
231
- if dual_output:
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
- except Exception:
234
- logger.error(traceback.format_exc())
235
- logger.error("TTS 失败,使用文本发送。")
237
+ else:
236
238
  new_chain.append(comp)
237
- else:
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
- if not has_forwarded:
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() == "webchat":
77
+ if event.get_platform_name() in ["webchat", "wecom_ai_bot"]:
78
78
  await event.send(None)
79
79
 
80
80
  logger.debug("pipeline 执行完毕。")
@@ -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}
@@ -91,7 +91,6 @@ class WebChatAdapter(Platform):
91
91
 
92
92
  abm = AstrBotMessage()
93
93
  abm.self_id = "webchat"
94
- abm.tag = "webchat"
95
94
  abm.sender = MessageMember(username, username)
96
95
 
97
96
  abm.type = MessageType.FRIEND_MESSAGE