AstrBot 4.7.4__py3-none-any.whl → 4.9.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.
Files changed (111) hide show
  1. astrbot/cli/__init__.py +1 -1
  2. astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
  3. astrbot/core/agent/tool.py +7 -2
  4. astrbot/core/astr_agent_run_util.py +15 -1
  5. astrbot/core/astr_agent_tool_exec.py +5 -1
  6. astrbot/core/config/astrbot_config.py +4 -0
  7. astrbot/core/config/default.py +116 -1
  8. astrbot/core/core_lifecycle.py +1 -1
  9. astrbot/core/db/__init__.py +32 -4
  10. astrbot/core/db/migration/migra_3_to_4.py +2 -0
  11. astrbot/core/db/migration/sqlite_v3.py +6 -4
  12. astrbot/core/db/po.py +16 -15
  13. astrbot/core/db/sqlite.py +56 -1
  14. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
  15. astrbot/core/event_bus.py +6 -1
  16. astrbot/core/knowledge_base/retrieval/manager.py +5 -1
  17. astrbot/core/log.py +2 -1
  18. astrbot/core/message/components.py +9 -3
  19. astrbot/core/persona_mgr.py +2 -2
  20. astrbot/core/pipeline/content_safety_check/stage.py +1 -1
  21. astrbot/core/pipeline/context_utils.py +2 -1
  22. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
  23. astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
  24. astrbot/core/pipeline/process_stage/stage.py +1 -1
  25. astrbot/core/pipeline/respond/stage.py +4 -2
  26. astrbot/core/pipeline/result_decorate/stage.py +68 -21
  27. astrbot/core/pipeline/scheduler.py +5 -1
  28. astrbot/core/pipeline/waking_check/stage.py +10 -0
  29. astrbot/core/platform/astr_message_event.py +5 -3
  30. astrbot/core/platform/astrbot_message.py +2 -2
  31. astrbot/core/platform/manager.py +71 -9
  32. astrbot/core/platform/platform.py +109 -4
  33. astrbot/core/platform/platform_metadata.py +1 -1
  34. astrbot/core/platform/register.py +1 -0
  35. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
  36. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +13 -8
  37. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +28 -22
  38. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
  39. astrbot/core/platform/sources/discord/client.py +16 -4
  40. astrbot/core/platform/sources/discord/components.py +2 -2
  41. astrbot/core/platform/sources/discord/discord_platform_adapter.py +53 -26
  42. astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
  43. astrbot/core/platform/sources/lark/lark_adapter.py +178 -22
  44. astrbot/core/platform/sources/lark/lark_event.py +39 -4
  45. astrbot/core/platform/sources/lark/server.py +206 -0
  46. astrbot/core/platform/sources/misskey/misskey_adapter.py +3 -5
  47. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +64 -18
  48. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +14 -10
  49. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -11
  50. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +15 -2
  51. astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
  52. astrbot/core/platform/sources/slack/client.py +58 -40
  53. astrbot/core/platform/sources/slack/slack_adapter.py +36 -16
  54. astrbot/core/platform/sources/slack/slack_event.py +11 -10
  55. astrbot/core/platform/sources/telegram/tg_adapter.py +2 -3
  56. astrbot/core/platform/sources/telegram/tg_event.py +23 -27
  57. astrbot/core/platform/sources/webchat/webchat_adapter.py +97 -31
  58. astrbot/core/platform/sources/webchat/webchat_event.py +35 -35
  59. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +27 -11
  60. astrbot/core/platform/sources/wecom/wecom_adapter.py +75 -36
  61. astrbot/core/platform/sources/wecom/wecom_event.py +3 -3
  62. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
  63. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
  64. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
  65. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +81 -35
  66. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +11 -8
  67. astrbot/core/platform_message_history_mgr.py +3 -3
  68. astrbot/core/provider/func_tool_manager.py +3 -3
  69. astrbot/core/provider/manager.py +130 -74
  70. astrbot/core/provider/provider.py +12 -1
  71. astrbot/core/provider/sources/azure_tts_source.py +31 -9
  72. astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
  73. astrbot/core/provider/sources/dashscope_tts.py +3 -2
  74. astrbot/core/provider/sources/edge_tts_source.py +1 -1
  75. astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
  76. astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
  77. astrbot/core/provider/sources/gemini_source.py +12 -10
  78. astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
  79. astrbot/core/provider/sources/openai_embedding_source.py +2 -2
  80. astrbot/core/provider/sources/openai_source.py +4 -0
  81. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
  82. astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
  83. astrbot/core/provider/sources/whisper_api_source.py +44 -12
  84. astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
  85. astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
  86. astrbot/core/star/context.py +2 -2
  87. astrbot/core/star/register/star_handler.py +22 -5
  88. astrbot/core/star/star_handler.py +85 -4
  89. astrbot/core/updator.py +3 -3
  90. astrbot/core/utils/io.py +1 -1
  91. astrbot/core/utils/session_waiter.py +17 -10
  92. astrbot/core/utils/shared_preferences.py +32 -0
  93. astrbot/core/utils/t2i/__init__.py +2 -2
  94. astrbot/core/utils/t2i/local_strategy.py +25 -31
  95. astrbot/core/utils/tencent_record_helper.py +2 -2
  96. astrbot/core/utils/version_comparator.py +6 -3
  97. astrbot/core/utils/webhook_utils.py +66 -0
  98. astrbot/dashboard/routes/__init__.py +2 -0
  99. astrbot/dashboard/routes/chat.py +311 -76
  100. astrbot/dashboard/routes/config.py +14 -5
  101. astrbot/dashboard/routes/knowledge_base.py +254 -79
  102. astrbot/dashboard/routes/log.py +13 -8
  103. astrbot/dashboard/routes/platform.py +100 -0
  104. astrbot/dashboard/routes/plugin.py +108 -51
  105. astrbot/dashboard/routes/route.py +2 -0
  106. astrbot/dashboard/server.py +9 -4
  107. {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/METADATA +50 -37
  108. {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/RECORD +111 -108
  109. {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
  110. {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
  111. {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
- # 同时运行HTTP服务器和队列监听器
432
- await asyncio.gather(
433
- self.server.start_server(),
434
- self.queue_listener.run(),
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("企业微信智能机器人适配器正在关闭...")
@@ -39,7 +39,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
39
39
 
40
40
  @staticmethod
41
41
  async def _send(
42
- message_chain: MessageChain,
42
+ message_chain: MessageChain | None,
43
43
  stream_id: str,
44
44
  queue_mgr: WecomAIQueueMgr,
45
45
  streaming: bool = False,
@@ -90,7 +90,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
90
90
 
91
91
  return data
92
92
 
93
- async def send(self, message: MessageChain):
93
+ async def send(self, message: MessageChain | None):
94
94
  """发送消息"""
95
95
  raw = self.message_obj.raw_message
96
96
  assert isinstance(raw, dict), (
@@ -98,7 +98,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
98
98
  )
99
99
  stream_id = raw.get("stream_id", self.session_id)
100
100
  await WecomAIBotMessageEvent._send(message, stream_id, self.queue_mgr)
101
- await super().send(message)
101
+ await super().send(MessageChain([]))
102
102
 
103
103
  async def send_streaming(self, generator, use_fallback=False):
104
104
  """流式发送消息,参考webchat的send_streaming设计"""
@@ -59,8 +59,19 @@ class WecomAIBotServer:
59
59
  )
60
60
 
61
61
  async def verify_url(self):
62
- """验证回调 URL"""
63
- args = quart.request.args
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
- args = quart.request.args
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 quart.request.get_data()
127
+ post_data = await request.get_data()
106
128
 
107
129
  # 确保 post_data 是 bytes 类型
108
130
  if isinstance(post_data, str):
@@ -1,6 +1,8 @@
1
1
  import asyncio
2
2
  import sys
3
3
  import uuid
4
+ from collections.abc import Awaitable, Callable
5
+ from typing import Any, cast
4
6
 
5
7
  import quart
6
8
  from requests import Response
@@ -22,6 +24,7 @@ from astrbot.api.platform import (
22
24
  )
23
25
  from astrbot.core import logger
24
26
  from astrbot.core.platform.astr_message_event import MessageSesion
27
+ from astrbot.core.utils.webhook_utils import log_webhook_info
25
28
 
26
29
  from .weixin_offacc_event import WeixinOfficialAccountPlatformEvent
27
30
 
@@ -31,10 +34,10 @@ else:
31
34
  from typing_extensions import override
32
35
 
33
36
 
34
- class WecomServer:
37
+ class WeixinOfficialAccountServer:
35
38
  def __init__(self, event_queue: asyncio.Queue, config: dict):
36
39
  self.server = quart.Quart(__name__)
37
- self.port = int(config.get("port"))
40
+ self.port = int(cast(int | str, config.get("port")))
38
41
  self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
39
42
  self.token = config.get("token")
40
43
  self.encoding_aes_key = config.get("encoding_aes_key")
@@ -53,13 +56,25 @@ class WecomServer:
53
56
 
54
57
  self.event_queue = event_queue
55
58
 
56
- self.callback = None
59
+ self.callback: Callable[[BaseMessage], Awaitable[None]] | None = None
57
60
  self.shutdown_event = asyncio.Event()
58
61
 
59
62
  async def verify(self):
60
- logger.info(f"验证请求有效性: {quart.request.args}")
63
+ """内部服务器的 GET 验证入口"""
64
+ return await self.handle_verify(quart.request)
61
65
 
62
- args = quart.request.args
66
+ async def handle_verify(self, request) -> str:
67
+ """处理验证请求,可被统一 webhook 入口复用
68
+
69
+ Args:
70
+ request: Quart 请求对象
71
+
72
+ Returns:
73
+ 验证响应
74
+ """
75
+ logger.info(f"验证请求有效性: {request.args}")
76
+
77
+ args = request.args
63
78
  if not args.get("signature", None):
64
79
  logger.error("未知的响应,请检查回调地址是否填写正确。")
65
80
  return "err"
@@ -77,10 +92,22 @@ class WecomServer:
77
92
  return "err"
78
93
 
79
94
  async def callback_command(self):
80
- data = await quart.request.get_data()
81
- msg_signature = quart.request.args.get("msg_signature")
82
- timestamp = quart.request.args.get("timestamp")
83
- nonce = quart.request.args.get("nonce")
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")
84
111
  try:
85
112
  xml = self.crypto.decrypt_message(data, msg_signature, timestamp, nonce)
86
113
  except InvalidSignatureException:
@@ -88,6 +115,9 @@ class WecomServer:
88
115
  raise
89
116
  else:
90
117
  msg = parse_message(xml)
118
+ if not msg:
119
+ logger.error("解析失败。msg为None。")
120
+ raise
91
121
  logger.info(f"解析成功: {msg}")
92
122
 
93
123
  if self.callback:
@@ -123,8 +153,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
123
153
  platform_settings: dict,
124
154
  event_queue: asyncio.Queue,
125
155
  ) -> None:
126
- super().__init__(event_queue)
127
- self.config = platform_config
156
+ super().__init__(platform_config, event_queue)
128
157
  self.settingss = platform_settings
129
158
  self.client_self_id = uuid.uuid4().hex[:8]
130
159
  self.api_base_url = platform_config.get(
@@ -132,6 +161,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
132
161
  "https://api.weixin.qq.com/cgi-bin/",
133
162
  )
134
163
  self.active_send_mode = self.config.get("active_send_mode", False)
164
+ self.unified_webhook_mode = platform_config.get("unified_webhook_mode", False)
135
165
 
136
166
  if not self.api_base_url:
137
167
  self.api_base_url = "https://api.weixin.qq.com/cgi-bin/"
@@ -143,14 +173,14 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
143
173
  if not self.api_base_url.endswith("/"):
144
174
  self.api_base_url += "/"
145
175
 
146
- self.server = WecomServer(self._event_queue, self.config)
176
+ self.server = WeixinOfficialAccountServer(self._event_queue, self.config)
147
177
 
148
178
  self.client = WeChatClient(
149
179
  self.config["appid"].strip(),
150
180
  self.config["secret"].strip(),
151
181
  )
152
182
 
153
- self.client.API_BASE_URL = self.api_base_url
183
+ self.client.__setattr__("API_BASE_URL", self.api_base_url)
154
184
 
155
185
  # 微信公众号必须 5 秒内进行回复,否则会重试 3 次,我们需要对其进行消息排重
156
186
  # msgid -> Future
@@ -162,11 +192,11 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
162
192
  await self.convert_message(msg, None)
163
193
  else:
164
194
  if msg.id in self.wexin_event_workers:
165
- future = self.wexin_event_workers[msg.id]
195
+ future = self.wexin_event_workers[str(cast(str | int, msg.id))]
166
196
  logger.debug(f"duplicate message id checked: {msg.id}")
167
197
  else:
168
198
  future = asyncio.get_event_loop().create_future()
169
- self.wexin_event_workers[msg.id] = future
199
+ self.wexin_event_workers[str(cast(str | int, msg.id))] = future
170
200
  await self.convert_message(msg, future)
171
201
  # I love shield so much!
172
202
  result = await asyncio.wait_for(
@@ -174,7 +204,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
174
204
  60,
175
205
  ) # wait for 60s
176
206
  logger.debug(f"Got future result: {result}")
177
- self.wexin_event_workers.pop(msg.id, None)
207
+ self.wexin_event_workers.pop(str(cast(str | int, msg.id)), None)
178
208
  return result # xml. see weixin_offacc_event.py
179
209
  except asyncio.TimeoutError:
180
210
  pass
@@ -202,38 +232,53 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
202
232
 
203
233
  @override
204
234
  async def run(self):
205
- await self.server.start_polling()
235
+ # 如果启用统一 webhook 模式,则不启动独立服务器
236
+ webhook_uuid = self.config.get("webhook_uuid")
237
+ if self.unified_webhook_mode and webhook_uuid:
238
+ log_webhook_info(f"{self.meta().id}(微信公众平台)", webhook_uuid)
239
+ # 保持运行状态,等待 shutdown
240
+ await self.server.shutdown_event.wait()
241
+ else:
242
+ await self.server.start_polling()
243
+
244
+ async def webhook_callback(self, request: Any) -> Any:
245
+ """统一 Webhook 回调入口"""
246
+ # 根据请求方法分发到不同的处理函数
247
+ if request.method == "GET":
248
+ return await self.server.handle_verify(request)
249
+ else:
250
+ return await self.server.handle_callback(request)
206
251
 
207
252
  async def convert_message(
208
253
  self,
209
254
  msg,
210
- future: asyncio.Future = None,
255
+ future: asyncio.Future | None = None,
211
256
  ) -> AstrBotMessage | None:
212
257
  abm = AstrBotMessage()
213
258
  if isinstance(msg, TextMessage):
214
- abm.message_str = msg.content
259
+ abm.message_str = cast(str, msg.content)
215
260
  abm.self_id = str(msg.target)
216
- abm.message = [Plain(msg.content)]
261
+ abm.message = [Plain(cast(str, msg.content))]
217
262
  abm.type = MessageType.FRIEND_MESSAGE
218
263
  abm.sender = MessageMember(
219
- msg.source,
220
- msg.source,
264
+ cast(str, msg.source),
265
+ cast(str, msg.source),
221
266
  )
222
- abm.message_id = msg.id
223
- abm.timestamp = msg.time
267
+ abm.message_id = str(cast(str | int, msg.id))
268
+ abm.timestamp = cast(int, msg.time)
224
269
  abm.session_id = abm.sender.user_id
225
270
  elif msg.type == "image":
226
271
  assert isinstance(msg, ImageMessage)
227
272
  abm.message_str = "[图片]"
228
273
  abm.self_id = str(msg.target)
229
- abm.message = [Image(file=msg.image, url=msg.image)]
274
+ abm.message = [Image(file=cast(str, msg.image), url=cast(str, msg.image))]
230
275
  abm.type = MessageType.FRIEND_MESSAGE
231
276
  abm.sender = MessageMember(
232
- msg.source,
233
- msg.source,
277
+ cast(str, msg.source),
278
+ cast(str, msg.source),
234
279
  )
235
- abm.message_id = msg.id
236
- abm.timestamp = msg.time
280
+ abm.message_id = str(cast(str | int, msg.id))
281
+ abm.timestamp = cast(int, msg.time)
237
282
  abm.session_id = abm.sender.user_id
238
283
  elif msg.type == "voice":
239
284
  assert isinstance(msg, VoiceMessage)
@@ -265,15 +310,16 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
265
310
  abm.message = [Record(file=path_wav, url=path_wav)]
266
311
  abm.type = MessageType.FRIEND_MESSAGE
267
312
  abm.sender = MessageMember(
268
- msg.source,
269
- msg.source,
313
+ cast(str, msg.source),
314
+ cast(str, msg.source),
270
315
  )
271
- abm.message_id = msg.id
272
- abm.timestamp = msg.time
316
+ abm.message_id = str(cast(str | int, msg.id))
317
+ abm.timestamp = cast(int, msg.time)
273
318
  abm.session_id = abm.sender.user_id
274
319
  else:
275
320
  logger.warning(f"暂未实现的事件: {msg.type}")
276
- future.set_result(None)
321
+ if future:
322
+ future.set_result(None)
277
323
  return
278
324
  # 很不优雅 :(
279
325
  abm.raw_message = {
@@ -303,4 +349,4 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
303
349
  await self.server.server.shutdown()
304
350
  except Exception as _:
305
351
  pass
306
- logger.info("微信公众平台 适配器已被优雅地关闭")
352
+ logger.info("微信公众平台 适配器已被关闭")
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import uuid
3
+ from typing import cast
3
4
 
4
5
  from wechatpy import WeChatClient
5
6
  from wechatpy.replies import ImageReply, TextReply, VoiceReply
@@ -13,7 +14,7 @@ try:
13
14
  import pydub
14
15
  except Exception:
15
16
  logger.warning(
16
- "检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。",
17
+ "检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 -> 平台日志 -> 安装 Pip 库安装 pydub。",
17
18
  )
18
19
 
19
20
 
@@ -85,7 +86,9 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
85
86
 
86
87
  async def send(self, message: MessageChain):
87
88
  message_obj = self.message_obj
88
- active_send_mode = message_obj.raw_message.get("active_send_mode", False)
89
+ active_send_mode = cast(dict, message_obj.raw_message).get(
90
+ "active_send_mode", False
91
+ )
89
92
  for comp in message.chain:
90
93
  if isinstance(comp, Plain):
91
94
  # Split long text messages if needed
@@ -96,10 +99,10 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
96
99
  else:
97
100
  reply = TextReply(
98
101
  content=chunk,
99
- message=self.message_obj.raw_message["message"],
102
+ message=cast(dict, self.message_obj.raw_message)["message"],
100
103
  )
101
104
  xml = reply.render()
102
- future = self.message_obj.raw_message["future"]
105
+ future = cast(dict, self.message_obj.raw_message)["future"]
103
106
  assert isinstance(future, asyncio.Future)
104
107
  future.set_result(xml)
105
108
  await asyncio.sleep(0.5) # Avoid sending too fast
@@ -125,10 +128,10 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
125
128
  else:
126
129
  reply = ImageReply(
127
130
  media_id=response["media_id"],
128
- message=self.message_obj.raw_message["message"],
131
+ message=cast(dict, self.message_obj.raw_message)["message"],
129
132
  )
130
133
  xml = reply.render()
131
- future = self.message_obj.raw_message["future"]
134
+ future = cast(dict, self.message_obj.raw_message)["future"]
132
135
  assert isinstance(future, asyncio.Future)
133
136
  future.set_result(xml)
134
137
 
@@ -160,10 +163,10 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
160
163
  else:
161
164
  reply = VoiceReply(
162
165
  media_id=response["media_id"],
163
- message=self.message_obj.raw_message["message"],
166
+ message=cast(dict, self.message_obj.raw_message)["message"],
164
167
  )
165
168
  xml = reply.render()
166
- future = self.message_obj.raw_message["future"]
169
+ future = cast(dict, self.message_obj.raw_message)["future"]
167
170
  assert isinstance(future, asyncio.Future)
168
171
  future.set_result(xml)
169
172
 
@@ -10,12 +10,12 @@ class PlatformMessageHistoryManager:
10
10
  self,
11
11
  platform_id: str,
12
12
  user_id: str,
13
- content: list[dict], # TODO: parse from message chain
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,
@@ -4,7 +4,7 @@ import asyncio
4
4
  import copy
5
5
  import json
6
6
  import os
7
- from collections.abc import Awaitable, Callable
7
+ from collections.abc import AsyncGenerator, Awaitable, Callable
8
8
  from typing import Any
9
9
 
10
10
  import aiohttp
@@ -118,7 +118,7 @@ class FunctionToolManager:
118
118
  name: str,
119
119
  func_args: list[dict],
120
120
  desc: str,
121
- handler: Callable[..., Awaitable[Any]],
121
+ handler: Callable[..., Awaitable[Any] | AsyncGenerator[Any]],
122
122
  ) -> FuncTool:
123
123
  params = {
124
124
  "type": "object", # hard-coded here
@@ -140,7 +140,7 @@ class FunctionToolManager:
140
140
  name: str,
141
141
  func_args: list,
142
142
  desc: str,
143
- handler: Callable[..., Awaitable[Any]],
143
+ handler: Callable[..., Awaitable[Any] | AsyncGenerator[Any]],
144
144
  ) -> None:
145
145
  """添加函数调用工具
146
146