AstrBot 4.5.1__py3-none-any.whl → 4.5.3__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 (244) hide show
  1. astrbot/api/__init__.py +10 -11
  2. astrbot/api/event/__init__.py +5 -6
  3. astrbot/api/event/filter/__init__.py +37 -36
  4. astrbot/api/platform/__init__.py +7 -8
  5. astrbot/api/provider/__init__.py +7 -7
  6. astrbot/api/star/__init__.py +3 -4
  7. astrbot/api/util/__init__.py +2 -2
  8. astrbot/cli/__main__.py +5 -5
  9. astrbot/cli/commands/__init__.py +3 -3
  10. astrbot/cli/commands/cmd_conf.py +19 -16
  11. astrbot/cli/commands/cmd_init.py +3 -2
  12. astrbot/cli/commands/cmd_plug.py +8 -10
  13. astrbot/cli/commands/cmd_run.py +5 -6
  14. astrbot/cli/utils/__init__.py +6 -6
  15. astrbot/cli/utils/basic.py +14 -14
  16. astrbot/cli/utils/plugin.py +24 -15
  17. astrbot/cli/utils/version_comparator.py +10 -12
  18. astrbot/core/__init__.py +8 -6
  19. astrbot/core/agent/agent.py +3 -2
  20. astrbot/core/agent/handoff.py +6 -2
  21. astrbot/core/agent/hooks.py +9 -6
  22. astrbot/core/agent/mcp_client.py +50 -15
  23. astrbot/core/agent/message.py +168 -0
  24. astrbot/core/agent/response.py +2 -1
  25. astrbot/core/agent/run_context.py +2 -3
  26. astrbot/core/agent/runners/base.py +10 -13
  27. astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
  28. astrbot/core/agent/tool.py +60 -41
  29. astrbot/core/agent/tool_executor.py +9 -3
  30. astrbot/core/astr_agent_context.py +3 -1
  31. astrbot/core/astrbot_config_mgr.py +29 -9
  32. astrbot/core/config/__init__.py +2 -2
  33. astrbot/core/config/astrbot_config.py +28 -26
  34. astrbot/core/config/default.py +4 -6
  35. astrbot/core/conversation_mgr.py +105 -36
  36. astrbot/core/core_lifecycle.py +68 -54
  37. astrbot/core/db/__init__.py +33 -18
  38. astrbot/core/db/migration/helper.py +12 -10
  39. astrbot/core/db/migration/migra_3_to_4.py +53 -34
  40. astrbot/core/db/migration/migra_45_to_46.py +1 -1
  41. astrbot/core/db/migration/shared_preferences_v3.py +2 -1
  42. astrbot/core/db/migration/sqlite_v3.py +26 -23
  43. astrbot/core/db/po.py +27 -18
  44. astrbot/core/db/sqlite.py +74 -45
  45. astrbot/core/db/vec_db/base.py +10 -14
  46. astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
  47. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
  48. astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
  49. astrbot/core/event_bus.py +8 -6
  50. astrbot/core/file_token_service.py +6 -5
  51. astrbot/core/initial_loader.py +7 -5
  52. astrbot/core/knowledge_base/chunking/__init__.py +1 -3
  53. astrbot/core/knowledge_base/chunking/base.py +1 -0
  54. astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
  55. astrbot/core/knowledge_base/chunking/recursive.py +16 -10
  56. astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
  57. astrbot/core/knowledge_base/kb_helper.py +30 -17
  58. astrbot/core/knowledge_base/kb_mgr.py +6 -7
  59. astrbot/core/knowledge_base/models.py +10 -4
  60. astrbot/core/knowledge_base/parsers/__init__.py +3 -5
  61. astrbot/core/knowledge_base/parsers/base.py +1 -0
  62. astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
  63. astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
  64. astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
  65. astrbot/core/knowledge_base/parsers/util.py +1 -1
  66. astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
  67. astrbot/core/knowledge_base/retrieval/manager.py +17 -14
  68. astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
  69. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
  70. astrbot/core/log.py +21 -13
  71. astrbot/core/message/components.py +123 -217
  72. astrbot/core/message/message_event_result.py +24 -24
  73. astrbot/core/persona_mgr.py +20 -11
  74. astrbot/core/pipeline/__init__.py +7 -7
  75. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  76. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  77. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
  78. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
  79. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  80. astrbot/core/pipeline/context.py +4 -1
  81. astrbot/core/pipeline/context_utils.py +77 -7
  82. astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
  83. astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
  84. astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
  85. astrbot/core/pipeline/process_stage/stage.py +13 -10
  86. astrbot/core/pipeline/process_stage/utils.py +6 -5
  87. astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
  88. astrbot/core/pipeline/respond/stage.py +23 -20
  89. astrbot/core/pipeline/result_decorate/stage.py +31 -23
  90. astrbot/core/pipeline/scheduler.py +12 -8
  91. astrbot/core/pipeline/session_status_check/stage.py +12 -8
  92. astrbot/core/pipeline/stage.py +10 -4
  93. astrbot/core/pipeline/waking_check/stage.py +24 -18
  94. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  95. astrbot/core/platform/__init__.py +6 -6
  96. astrbot/core/platform/astr_message_event.py +76 -110
  97. astrbot/core/platform/astrbot_message.py +11 -13
  98. astrbot/core/platform/manager.py +16 -15
  99. astrbot/core/platform/message_session.py +5 -3
  100. astrbot/core/platform/platform.py +16 -24
  101. astrbot/core/platform/platform_metadata.py +4 -4
  102. astrbot/core/platform/register.py +8 -8
  103. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
  104. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
  105. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
  106. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
  107. astrbot/core/platform/sources/discord/client.py +9 -6
  108. astrbot/core/platform/sources/discord/components.py +18 -14
  109. astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
  110. astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
  111. astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
  112. astrbot/core/platform/sources/lark/lark_event.py +21 -14
  113. astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
  114. astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
  115. astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
  116. astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
  117. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
  118. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  119. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  120. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  121. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
  122. astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
  123. astrbot/core/platform/sources/satori/satori_event.py +34 -25
  124. astrbot/core/platform/sources/slack/client.py +11 -9
  125. astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
  126. astrbot/core/platform/sources/slack/slack_event.py +34 -24
  127. astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
  128. astrbot/core/platform/sources/telegram/tg_event.py +32 -18
  129. astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
  130. astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
  131. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
  132. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
  133. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
  134. astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
  135. astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
  136. astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
  137. astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
  138. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
  139. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
  140. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
  141. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
  142. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
  143. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
  144. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
  145. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
  146. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
  147. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
  148. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
  149. astrbot/core/platform_message_history_mgr.py +5 -3
  150. astrbot/core/provider/__init__.py +2 -3
  151. astrbot/core/provider/entites.py +8 -8
  152. astrbot/core/provider/entities.py +61 -75
  153. astrbot/core/provider/func_tool_manager.py +59 -55
  154. astrbot/core/provider/manager.py +32 -22
  155. astrbot/core/provider/provider.py +72 -46
  156. astrbot/core/provider/register.py +7 -7
  157. astrbot/core/provider/sources/anthropic_source.py +48 -30
  158. astrbot/core/provider/sources/azure_tts_source.py +17 -13
  159. astrbot/core/provider/sources/coze_api_client.py +27 -17
  160. astrbot/core/provider/sources/coze_source.py +104 -87
  161. astrbot/core/provider/sources/dashscope_source.py +18 -11
  162. astrbot/core/provider/sources/dashscope_tts.py +36 -23
  163. astrbot/core/provider/sources/dify_source.py +25 -20
  164. astrbot/core/provider/sources/edge_tts_source.py +21 -17
  165. astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
  166. astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
  167. astrbot/core/provider/sources/gemini_source.py +72 -58
  168. astrbot/core/provider/sources/gemini_tts_source.py +8 -6
  169. astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
  170. astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
  171. astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
  172. astrbot/core/provider/sources/openai_embedding_source.py +6 -8
  173. astrbot/core/provider/sources/openai_source.py +77 -69
  174. astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
  175. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  176. astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
  177. astrbot/core/provider/sources/volcengine_tts.py +38 -31
  178. astrbot/core/provider/sources/whisper_api_source.py +14 -12
  179. astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
  180. astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
  181. astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
  182. astrbot/core/star/__init__.py +16 -11
  183. astrbot/core/star/config.py +10 -15
  184. astrbot/core/star/context.py +97 -75
  185. astrbot/core/star/filter/__init__.py +4 -3
  186. astrbot/core/star/filter/command.py +30 -28
  187. astrbot/core/star/filter/command_group.py +27 -24
  188. astrbot/core/star/filter/custom_filter.py +6 -5
  189. astrbot/core/star/filter/event_message_type.py +4 -2
  190. astrbot/core/star/filter/permission.py +4 -2
  191. astrbot/core/star/filter/platform_adapter_type.py +4 -2
  192. astrbot/core/star/filter/regex.py +4 -2
  193. astrbot/core/star/register/__init__.py +19 -19
  194. astrbot/core/star/register/star.py +6 -2
  195. astrbot/core/star/register/star_handler.py +96 -73
  196. astrbot/core/star/session_llm_manager.py +48 -14
  197. astrbot/core/star/session_plugin_manager.py +29 -15
  198. astrbot/core/star/star.py +1 -2
  199. astrbot/core/star/star_handler.py +13 -8
  200. astrbot/core/star/star_manager.py +151 -59
  201. astrbot/core/star/star_tools.py +44 -37
  202. astrbot/core/star/updator.py +10 -10
  203. astrbot/core/umop_config_router.py +10 -4
  204. astrbot/core/updator.py +13 -5
  205. astrbot/core/utils/astrbot_path.py +3 -5
  206. astrbot/core/utils/dify_api_client.py +33 -15
  207. astrbot/core/utils/io.py +66 -42
  208. astrbot/core/utils/log_pipe.py +1 -1
  209. astrbot/core/utils/metrics.py +7 -7
  210. astrbot/core/utils/path_util.py +15 -16
  211. astrbot/core/utils/pip_installer.py +5 -5
  212. astrbot/core/utils/session_waiter.py +19 -20
  213. astrbot/core/utils/shared_preferences.py +45 -20
  214. astrbot/core/utils/t2i/__init__.py +4 -1
  215. astrbot/core/utils/t2i/network_strategy.py +35 -26
  216. astrbot/core/utils/t2i/renderer.py +11 -5
  217. astrbot/core/utils/t2i/template_manager.py +14 -15
  218. astrbot/core/utils/tencent_record_helper.py +19 -13
  219. astrbot/core/utils/version_comparator.py +10 -13
  220. astrbot/core/zip_updator.py +43 -40
  221. astrbot/dashboard/routes/__init__.py +18 -18
  222. astrbot/dashboard/routes/auth.py +10 -8
  223. astrbot/dashboard/routes/chat.py +30 -21
  224. astrbot/dashboard/routes/config.py +92 -75
  225. astrbot/dashboard/routes/conversation.py +46 -39
  226. astrbot/dashboard/routes/file.py +4 -2
  227. astrbot/dashboard/routes/knowledge_base.py +47 -40
  228. astrbot/dashboard/routes/log.py +9 -4
  229. astrbot/dashboard/routes/persona.py +19 -16
  230. astrbot/dashboard/routes/plugin.py +69 -55
  231. astrbot/dashboard/routes/route.py +3 -1
  232. astrbot/dashboard/routes/session_management.py +130 -116
  233. astrbot/dashboard/routes/stat.py +34 -34
  234. astrbot/dashboard/routes/t2i.py +15 -12
  235. astrbot/dashboard/routes/tools.py +56 -53
  236. astrbot/dashboard/routes/update.py +32 -28
  237. astrbot/dashboard/server.py +30 -26
  238. astrbot/dashboard/utils.py +8 -4
  239. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/METADATA +2 -1
  240. astrbot-4.5.3.dist-info/RECORD +261 -0
  241. astrbot-4.5.1.dist-info/RECORD +0 -260
  242. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,20 @@
1
- """
2
- 企业微信智能机器人 API 客户端
1
+ """企业微信智能机器人 API 客户端
3
2
  处理消息加密解密、API 调用等
4
3
  """
5
4
 
6
- import json
7
5
  import base64
8
6
  import hashlib
9
- from typing import Dict, Any, Optional, Tuple, Union
10
- from Crypto.Cipher import AES
7
+ import json
8
+ from typing import Any
9
+
11
10
  import aiohttp
11
+ from Crypto.Cipher import AES
12
12
 
13
- from .WXBizJsonMsgCrypt import WXBizJsonMsgCrypt
14
- from .wecomai_utils import WecomAIBotConstants
15
13
  from astrbot import logger
16
14
 
15
+ from .wecomai_utils import WecomAIBotConstants
16
+ from .WXBizJsonMsgCrypt import WXBizJsonMsgCrypt
17
+
17
18
 
18
19
  class WecomAIBotAPIClient:
19
20
  """企业微信智能机器人 API 客户端"""
@@ -24,14 +25,19 @@ class WecomAIBotAPIClient:
24
25
  Args:
25
26
  token: 企业微信机器人 Token
26
27
  encoding_aes_key: 消息加密密钥
28
+
27
29
  """
28
30
  self.token = token
29
31
  self.encoding_aes_key = encoding_aes_key
30
32
  self.wxcpt = WXBizJsonMsgCrypt(token, encoding_aes_key, "") # receiveid 为空串
31
33
 
32
34
  async def decrypt_message(
33
- self, encrypted_data: bytes, msg_signature: str, timestamp: str, nonce: str
34
- ) -> Tuple[int, Optional[Dict[str, Any]]]:
35
+ self,
36
+ encrypted_data: bytes,
37
+ msg_signature: str,
38
+ timestamp: str,
39
+ nonce: str,
40
+ ) -> tuple[int, dict[str, Any] | None]:
35
41
  """解密企业微信消息
36
42
 
37
43
  Args:
@@ -42,10 +48,14 @@ class WecomAIBotAPIClient:
42
48
 
43
49
  Returns:
44
50
  (错误码, 解密后的消息数据字典)
51
+
45
52
  """
46
53
  try:
47
54
  ret, decrypted_msg = self.wxcpt.DecryptMsg(
48
- encrypted_data, msg_signature, timestamp, nonce
55
+ encrypted_data,
56
+ msg_signature,
57
+ timestamp,
58
+ nonce,
49
59
  )
50
60
 
51
61
  if ret != WecomAIBotConstants.SUCCESS:
@@ -70,8 +80,11 @@ class WecomAIBotAPIClient:
70
80
  return WecomAIBotConstants.DECRYPT_ERROR, None
71
81
 
72
82
  async def encrypt_message(
73
- self, plain_message: str, nonce: str, timestamp: str
74
- ) -> Optional[str]:
83
+ self,
84
+ plain_message: str,
85
+ nonce: str,
86
+ timestamp: str,
87
+ ) -> str | None:
75
88
  """加密消息
76
89
 
77
90
  Args:
@@ -81,6 +94,7 @@ class WecomAIBotAPIClient:
81
94
 
82
95
  Returns:
83
96
  加密后的消息,失败时返回 None
97
+
84
98
  """
85
99
  try:
86
100
  ret, encrypted_msg = self.wxcpt.EncryptMsg(plain_message, nonce, timestamp)
@@ -97,7 +111,11 @@ class WecomAIBotAPIClient:
97
111
  return None
98
112
 
99
113
  def verify_url(
100
- self, msg_signature: str, timestamp: str, nonce: str, echostr: str
114
+ self,
115
+ msg_signature: str,
116
+ timestamp: str,
117
+ nonce: str,
118
+ echostr: str,
101
119
  ) -> str:
102
120
  """验证回调 URL
103
121
 
@@ -109,10 +127,14 @@ class WecomAIBotAPIClient:
109
127
 
110
128
  Returns:
111
129
  验证结果字符串
130
+
112
131
  """
113
132
  try:
114
133
  ret, echo_result = self.wxcpt.VerifyURL(
115
- msg_signature, timestamp, nonce, echostr
134
+ msg_signature,
135
+ timestamp,
136
+ nonce,
137
+ echostr,
116
138
  )
117
139
 
118
140
  if ret != WecomAIBotConstants.SUCCESS:
@@ -127,8 +149,10 @@ class WecomAIBotAPIClient:
127
149
  return "verify fail"
128
150
 
129
151
  async def process_encrypted_image(
130
- self, image_url: str, aes_key_base64: Optional[str] = None
131
- ) -> Tuple[bool, Union[bytes, str]]:
152
+ self,
153
+ image_url: str,
154
+ aes_key_base64: str | None = None,
155
+ ) -> tuple[bool, bytes | str]:
132
156
  """下载并解密加密图片
133
157
 
134
158
  Args:
@@ -137,6 +161,7 @@ class WecomAIBotAPIClient:
137
161
 
138
162
  Returns:
139
163
  (是否成功, 图片数据或错误信息)
164
+
140
165
  """
141
166
  try:
142
167
  # 下载图片
@@ -161,7 +186,7 @@ class WecomAIBotAPIClient:
161
186
 
162
187
  # Base64 解码密钥
163
188
  aes_key = base64.b64decode(
164
- aes_key_base64 + "=" * (-len(aes_key_base64) % 4)
189
+ aes_key_base64 + "=" * (-len(aes_key_base64) % 4),
165
190
  )
166
191
  if len(aes_key) != 32:
167
192
  raise ValueError("无效的 AES 密钥长度: 应为 32 字节")
@@ -183,17 +208,17 @@ class WecomAIBotAPIClient:
183
208
  return True, decrypted_data
184
209
 
185
210
  except aiohttp.ClientError as e:
186
- error_msg = f"图片下载失败: {str(e)}"
211
+ error_msg = f"图片下载失败: {e!s}"
187
212
  logger.error(error_msg)
188
213
  return False, error_msg
189
214
 
190
215
  except ValueError as e:
191
- error_msg = f"参数错误: {str(e)}"
216
+ error_msg = f"参数错误: {e!s}"
192
217
  logger.error(error_msg)
193
218
  return False, error_msg
194
219
 
195
220
  except Exception as e:
196
- error_msg = f"图片处理异常: {str(e)}"
221
+ error_msg = f"图片处理异常: {e!s}"
197
222
  logger.error(error_msg)
198
223
  return False, error_msg
199
224
 
@@ -212,6 +237,7 @@ class WecomAIBotStreamMessageBuilder:
212
237
 
213
238
  Returns:
214
239
  JSON 格式的流消息字符串
240
+
215
241
  """
216
242
  plain = {
217
243
  "msgtype": WecomAIBotConstants.MSG_TYPE_STREAM,
@@ -221,7 +247,9 @@ class WecomAIBotStreamMessageBuilder:
221
247
 
222
248
  @staticmethod
223
249
  def make_image_stream(
224
- stream_id: str, image_data: bytes, finish: bool = False
250
+ stream_id: str,
251
+ image_data: bytes,
252
+ finish: bool = False,
225
253
  ) -> str:
226
254
  """构建图片流消息
227
255
 
@@ -232,6 +260,7 @@ class WecomAIBotStreamMessageBuilder:
232
260
 
233
261
  Returns:
234
262
  JSON 格式的流消息字符串
263
+
235
264
  """
236
265
  image_md5 = hashlib.md5(image_data).hexdigest()
237
266
  image_base64 = base64.b64encode(image_data).decode("utf-8")
@@ -245,7 +274,7 @@ class WecomAIBotStreamMessageBuilder:
245
274
  {
246
275
  "msgtype": WecomAIBotConstants.MSG_TYPE_IMAGE,
247
276
  "image": {"base64": image_base64, "md5": image_md5},
248
- }
277
+ },
249
278
  ],
250
279
  },
251
280
  }
@@ -253,7 +282,10 @@ class WecomAIBotStreamMessageBuilder:
253
282
 
254
283
  @staticmethod
255
284
  def make_mixed_stream(
256
- stream_id: str, content: str, msg_items: list, finish: bool = False
285
+ stream_id: str,
286
+ content: str,
287
+ msg_items: list,
288
+ finish: bool = False,
257
289
  ) -> str:
258
290
  """构建混合类型流消息
259
291
 
@@ -265,6 +297,7 @@ class WecomAIBotStreamMessageBuilder:
265
297
 
266
298
  Returns:
267
299
  JSON 格式的流消息字符串
300
+
268
301
  """
269
302
  plain = {
270
303
  "msgtype": WecomAIBotConstants.MSG_TYPE_STREAM,
@@ -283,6 +316,7 @@ class WecomAIBotStreamMessageBuilder:
283
316
 
284
317
  Returns:
285
318
  JSON 格式的文本消息字符串
319
+
286
320
  """
287
321
  plain = {"msgtype": "text", "text": {"content": content}}
288
322
  return json.dumps(plain, ensure_ascii=False)
@@ -292,7 +326,7 @@ class WecomAIBotMessageParser:
292
326
  """企业微信智能机器人消息解析器"""
293
327
 
294
328
  @staticmethod
295
- def parse_text_message(data: Dict[str, Any]) -> Optional[str]:
329
+ def parse_text_message(data: dict[str, Any]) -> str | None:
296
330
  """解析文本消息
297
331
 
298
332
  Args:
@@ -300,6 +334,7 @@ class WecomAIBotMessageParser:
300
334
 
301
335
  Returns:
302
336
  文本内容,解析失败返回 None
337
+
303
338
  """
304
339
  try:
305
340
  return data.get("text", {}).get("content")
@@ -308,7 +343,7 @@ class WecomAIBotMessageParser:
308
343
  return None
309
344
 
310
345
  @staticmethod
311
- def parse_image_message(data: Dict[str, Any]) -> Optional[str]:
346
+ def parse_image_message(data: dict[str, Any]) -> str | None:
312
347
  """解析图片消息
313
348
 
314
349
  Args:
@@ -316,6 +351,7 @@ class WecomAIBotMessageParser:
316
351
 
317
352
  Returns:
318
353
  图片 URL,解析失败返回 None
354
+
319
355
  """
320
356
  try:
321
357
  return data.get("image", {}).get("url")
@@ -324,7 +360,7 @@ class WecomAIBotMessageParser:
324
360
  return None
325
361
 
326
362
  @staticmethod
327
- def parse_stream_message(data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
363
+ def parse_stream_message(data: dict[str, Any]) -> dict[str, Any] | None:
328
364
  """解析流消息
329
365
 
330
366
  Args:
@@ -332,6 +368,7 @@ class WecomAIBotMessageParser:
332
368
 
333
369
  Returns:
334
370
  流消息数据,解析失败返回 None
371
+
335
372
  """
336
373
  try:
337
374
  stream_data = data.get("stream", {})
@@ -346,7 +383,7 @@ class WecomAIBotMessageParser:
346
383
  return None
347
384
 
348
385
  @staticmethod
349
- def parse_mixed_message(data: Dict[str, Any]) -> Optional[list]:
386
+ def parse_mixed_message(data: dict[str, Any]) -> list | None:
350
387
  """解析混合消息
351
388
 
352
389
  Args:
@@ -354,6 +391,7 @@ class WecomAIBotMessageParser:
354
391
 
355
392
  Returns:
356
393
  消息项列表,解析失败返回 None
394
+
357
395
  """
358
396
  try:
359
397
  return data.get("mixed", {}).get("msg_item", [])
@@ -362,7 +400,7 @@ class WecomAIBotMessageParser:
362
400
  return None
363
401
 
364
402
  @staticmethod
365
- def parse_event_message(data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
403
+ def parse_event_message(data: dict[str, Any]) -> dict[str, Any] | None:
366
404
  """解析事件消息
367
405
 
368
406
  Args:
@@ -370,6 +408,7 @@ class WecomAIBotMessageParser:
370
408
 
371
409
  Returns:
372
410
  事件数据,解析失败返回 None
411
+
373
412
  """
374
413
  try:
375
414
  return data.get("event", {})
@@ -1,13 +1,11 @@
1
- """
2
- 企业微信智能机器人事件处理模块,处理消息事件的发送和接收
3
- """
1
+ """企业微信智能机器人事件处理模块,处理消息事件的发送和接收"""
4
2
 
3
+ from astrbot.api import logger
5
4
  from astrbot.api.event import AstrMessageEvent, MessageChain
6
5
  from astrbot.api.message_components import (
7
6
  Image,
8
7
  Plain,
9
8
  )
10
- from astrbot.api import logger
11
9
 
12
10
  from .wecomai_api import WecomAIBotAPIClient
13
11
  from .wecomai_queue_mgr import wecomai_queue_mgr
@@ -32,6 +30,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
32
30
  platform_meta: 平台元数据
33
31
  session_id: 会话 ID
34
32
  api_client: API 客户端
33
+
35
34
  """
36
35
  super().__init__(message_str, message_obj, platform_meta, session_id)
37
36
  self.api_client = api_client
@@ -50,7 +49,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
50
49
  "type": "end",
51
50
  "data": "",
52
51
  "streaming": False,
53
- }
52
+ },
54
53
  )
55
54
  return ""
56
55
 
@@ -64,7 +63,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
64
63
  "data": data,
65
64
  "streaming": streaming,
66
65
  "session_id": stream_id,
67
- }
66
+ },
68
67
  )
69
68
  elif isinstance(comp, Image):
70
69
  # 处理图片消息
@@ -77,7 +76,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
77
76
  "image_data": image_base64,
78
77
  "streaming": streaming,
79
78
  "session_id": stream_id,
80
- }
79
+ },
81
80
  )
82
81
  else:
83
82
  logger.warning("图片数据为空,跳过")
@@ -127,7 +126,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
127
126
  "data": final_data,
128
127
  "streaming": True,
129
128
  "session_id": self.session_id,
130
- }
129
+ },
131
130
  )
132
131
  final_data = ""
133
132
  continue
@@ -144,6 +143,6 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
144
143
  "data": final_data,
145
144
  "streaming": True,
146
145
  "session_id": self.session_id,
147
- }
146
+ },
148
147
  )
149
148
  await super().send_streaming(generator, use_fallback)
@@ -1,11 +1,11 @@
1
- """
2
- 企业微信智能机器人队列管理器
1
+ """企业微信智能机器人队列管理器
3
2
  参考 webchat_queue_mgr.py,为企业微信智能机器人实现队列机制
4
3
  支持异步消息处理和流式响应
5
4
  """
6
5
 
7
6
  import asyncio
8
- from typing import Dict, Any, Optional
7
+ from typing import Any
8
+
9
9
  from astrbot.api import logger
10
10
 
11
11
 
@@ -13,13 +13,13 @@ class WecomAIQueueMgr:
13
13
  """企业微信智能机器人队列管理器"""
14
14
 
15
15
  def __init__(self) -> None:
16
- self.queues: Dict[str, asyncio.Queue] = {}
16
+ self.queues: dict[str, asyncio.Queue] = {}
17
17
  """StreamID 到输入队列的映射 - 用于接收用户消息"""
18
18
 
19
- self.back_queues: Dict[str, asyncio.Queue] = {}
19
+ self.back_queues: dict[str, asyncio.Queue] = {}
20
20
  """StreamID 到输出队列的映射 - 用于发送机器人响应"""
21
21
 
22
- self.pending_responses: Dict[str, Dict[str, Any]] = {}
22
+ self.pending_responses: dict[str, dict[str, Any]] = {}
23
23
  """待处理的响应缓存,用于流式响应"""
24
24
 
25
25
  def get_or_create_queue(self, session_id: str) -> asyncio.Queue:
@@ -30,6 +30,7 @@ class WecomAIQueueMgr:
30
30
 
31
31
  Returns:
32
32
  输入队列实例
33
+
33
34
  """
34
35
  if session_id not in self.queues:
35
36
  self.queues[session_id] = asyncio.Queue()
@@ -44,6 +45,7 @@ class WecomAIQueueMgr:
44
45
 
45
46
  Returns:
46
47
  输出队列实例
48
+
47
49
  """
48
50
  if session_id not in self.back_queues:
49
51
  self.back_queues[session_id] = asyncio.Queue()
@@ -55,6 +57,7 @@ class WecomAIQueueMgr:
55
57
 
56
58
  Args:
57
59
  session_id: 会话ID
60
+
58
61
  """
59
62
  if session_id in self.queues:
60
63
  del self.queues[session_id]
@@ -76,6 +79,7 @@ class WecomAIQueueMgr:
76
79
 
77
80
  Returns:
78
81
  是否存在队列
82
+
79
83
  """
80
84
  return session_id in self.queues
81
85
 
@@ -87,15 +91,17 @@ class WecomAIQueueMgr:
87
91
 
88
92
  Returns:
89
93
  是否存在输出队列
94
+
90
95
  """
91
96
  return session_id in self.back_queues
92
97
 
93
- def set_pending_response(self, session_id: str, callback_params: Dict[str, str]):
98
+ def set_pending_response(self, session_id: str, callback_params: dict[str, str]):
94
99
  """设置待处理的响应参数
95
100
 
96
101
  Args:
97
102
  session_id: 会话ID
98
103
  callback_params: 回调参数(nonce, timestamp等)
104
+
99
105
  """
100
106
  self.pending_responses[session_id] = {
101
107
  "callback_params": callback_params,
@@ -103,7 +109,7 @@ class WecomAIQueueMgr:
103
109
  }
104
110
  logger.debug(f"[WecomAI] 设置待处理响应: {session_id}")
105
111
 
106
- def get_pending_response(self, session_id: str) -> Optional[Dict[str, Any]]:
112
+ def get_pending_response(self, session_id: str) -> dict[str, Any] | None:
107
113
  """获取待处理的响应参数
108
114
 
109
115
  Args:
@@ -111,6 +117,7 @@ class WecomAIQueueMgr:
111
117
 
112
118
  Returns:
113
119
  响应参数,如果不存在则返回None
120
+
114
121
  """
115
122
  return self.pending_responses.get(session_id)
116
123
 
@@ -119,6 +126,7 @@ class WecomAIQueueMgr:
119
126
 
120
127
  Args:
121
128
  max_age_seconds: 最大存活时间(秒)
129
+
122
130
  """
123
131
  current_time = asyncio.get_event_loop().time()
124
132
  expired_sessions = []
@@ -131,11 +139,12 @@ class WecomAIQueueMgr:
131
139
  del self.pending_responses[session_id]
132
140
  logger.debug(f"[WecomAI] 清理过期响应: {session_id}")
133
141
 
134
- def get_stats(self) -> Dict[str, int]:
142
+ def get_stats(self) -> dict[str, int]:
135
143
  """获取队列统计信息
136
144
 
137
145
  Returns:
138
146
  统计信息字典
147
+
139
148
  """
140
149
  return {
141
150
  "input_queues": len(self.queues),
@@ -1,12 +1,13 @@
1
- """
2
- 企业微信智能机器人 HTTP 服务器
1
+ """企业微信智能机器人 HTTP 服务器
3
2
  处理企业微信智能机器人的 HTTP 回调请求
4
3
  """
5
4
 
6
5
  import asyncio
7
- from typing import Dict, Any, Optional, Callable
6
+ from collections.abc import Callable
7
+ from typing import Any
8
8
 
9
9
  import quart
10
+
10
11
  from astrbot.api import logger
11
12
 
12
13
  from .wecomai_api import WecomAIBotAPIClient
@@ -21,9 +22,7 @@ class WecomAIBotServer:
21
22
  host: str,
22
23
  port: int,
23
24
  api_client: WecomAIBotAPIClient,
24
- message_handler: Optional[
25
- Callable[[Dict[str, Any], Dict[str, str]], Any]
26
- ] = None,
25
+ message_handler: Callable[[dict[str, Any], dict[str, str]], Any] | None = None,
27
26
  ):
28
27
  """初始化服务器
29
28
 
@@ -32,6 +31,7 @@ class WecomAIBotServer:
32
31
  port: 监听端口
33
32
  api_client: API客户端实例
34
33
  message_handler: 消息处理回调函数
34
+
35
35
  """
36
36
  self.host = host
37
37
  self.port = port
@@ -45,7 +45,6 @@ class WecomAIBotServer:
45
45
 
46
46
  def _setup_routes(self):
47
47
  """设置 Quart 路由"""
48
-
49
48
  # 使用 Quart 的 add_url_rule 方法添加路由
50
49
  self.app.add_url_rule(
51
50
  "/webhook/wecom-ai-bot",
@@ -98,7 +97,7 @@ class WecomAIBotServer:
98
97
  assert nonce is not None
99
98
 
100
99
  logger.debug(
101
- f"收到消息回调,msg_signature={msg_signature}, timestamp={timestamp}, nonce={nonce}"
100
+ f"收到消息回调,msg_signature={msg_signature}, timestamp={timestamp}, nonce={nonce}",
102
101
  )
103
102
 
104
103
  try:
@@ -111,7 +110,10 @@ class WecomAIBotServer:
111
110
 
112
111
  # 解密消息
113
112
  ret_code, message_data = await self.api_client.decrypt_message(
114
- post_data, msg_signature, timestamp, nonce
113
+ post_data,
114
+ msg_signature,
115
+ timestamp,
116
+ nonce,
115
117
  )
116
118
 
117
119
  if ret_code != WecomAIBotConstants.SUCCESS or not message_data:
@@ -123,7 +125,8 @@ class WecomAIBotServer:
123
125
  if self.message_handler:
124
126
  try:
125
127
  response = await self.message_handler(
126
- message_data, {"nonce": nonce, "timestamp": timestamp}
128
+ message_data,
129
+ {"nonce": nonce, "timestamp": timestamp},
127
130
  )
128
131
  except Exception as e:
129
132
  logger.error("消息处理器执行异常: %s", e)
@@ -131,8 +134,7 @@ class WecomAIBotServer:
131
134
 
132
135
  if response:
133
136
  return response, 200, {"Content-Type": "text/plain"}
134
- else:
135
- return "success", 200, {"Content-Type": "text/plain"}
137
+ return "success", 200, {"Content-Type": "text/plain"}
136
138
 
137
139
  except Exception as e:
138
140
  logger.error("处理消息时发生异常: %s", e)
@@ -1,16 +1,17 @@
1
- """
2
- 企业微信智能机器人工具模块
1
+ """企业微信智能机器人工具模块
3
2
  提供常量定义、工具函数和辅助方法
4
3
  """
5
4
 
6
- import string
7
- import random
8
- import hashlib
5
+ import asyncio
9
6
  import base64
7
+ import hashlib
8
+ import secrets
9
+ import string
10
+ from typing import Any
11
+
10
12
  import aiohttp
11
- import asyncio
12
13
  from Crypto.Cipher import AES
13
- from typing import Any, Tuple
14
+
14
15
  from astrbot.api import logger
15
16
 
16
17
 
@@ -49,9 +50,10 @@ def generate_random_string(length: int = 10) -> str:
49
50
 
50
51
  Returns:
51
52
  随机字符串
53
+
52
54
  """
53
55
  letters = string.ascii_letters + string.digits
54
- return "".join(random.choice(letters) for _ in range(length))
56
+ return "".join(secrets.choice(letters) for _ in range(length))
55
57
 
56
58
 
57
59
  def calculate_image_md5(image_data: bytes) -> str:
@@ -62,6 +64,7 @@ def calculate_image_md5(image_data: bytes) -> str:
62
64
 
63
65
  Returns:
64
66
  MD5 哈希值(十六进制字符串)
67
+
65
68
  """
66
69
  return hashlib.md5(image_data).hexdigest()
67
70
 
@@ -74,6 +77,7 @@ def encode_image_base64(image_data: bytes) -> str:
74
77
 
75
78
  Returns:
76
79
  Base64 编码的字符串
80
+
77
81
  """
78
82
  return base64.b64encode(image_data).decode("utf-8")
79
83
 
@@ -87,11 +91,12 @@ def format_session_id(session_type: str, session_id: str) -> str:
87
91
 
88
92
  Returns:
89
93
  格式化后的会话 ID
94
+
90
95
  """
91
96
  return f"wecom_ai_bot_{session_type}_{session_id}"
92
97
 
93
98
 
94
- def parse_session_id(formatted_session_id: str) -> Tuple[str, str]:
99
+ def parse_session_id(formatted_session_id: str) -> tuple[str, str]:
95
100
  """解析格式化的会话 ID
96
101
 
97
102
  Args:
@@ -99,6 +104,7 @@ def parse_session_id(formatted_session_id: str) -> Tuple[str, str]:
99
104
 
100
105
  Returns:
101
106
  (会话类型, 原始会话ID)
107
+
102
108
  """
103
109
  parts = formatted_session_id.split("_", 3)
104
110
  if (
@@ -120,6 +126,7 @@ def safe_json_loads(json_str: str, default: Any = None) -> Any:
120
126
 
121
127
  Returns:
122
128
  解析结果或默认值
129
+
123
130
  """
124
131
  import json
125
132
 
@@ -139,13 +146,15 @@ def format_error_response(error_code: int, error_msg: str) -> str:
139
146
 
140
147
  Returns:
141
148
  格式化的错误响应字符串
149
+
142
150
  """
143
151
  return f"Error {error_code}: {error_msg}"
144
152
 
145
153
 
146
154
  async def process_encrypted_image(
147
- image_url: str, aes_key_base64: str
148
- ) -> Tuple[bool, str]:
155
+ image_url: str,
156
+ aes_key_base64: str,
157
+ ) -> tuple[bool, str]:
149
158
  """下载并解密加密图片
150
159
 
151
160
  Args:
@@ -155,6 +164,7 @@ async def process_encrypted_image(
155
164
  Returns:
156
165
  Tuple[bool, str]: status 为 True 时 data 是解密后的图片数据的 base64 编码,
157
166
  status 为 False 时 data 是错误信息
167
+
158
168
  """
159
169
  # 1. 下载加密图片
160
170
  logger.info("开始下载加密图片: %s", image_url)
@@ -165,7 +175,7 @@ async def process_encrypted_image(
165
175
  encrypted_data = await response.read()
166
176
  logger.info("图片下载成功,大小: %d 字节", len(encrypted_data))
167
177
  except (aiohttp.ClientError, asyncio.TimeoutError) as e:
168
- error_msg = f"下载图片失败: {str(e)}"
178
+ error_msg = f"下载图片失败: {e!s}"
169
179
  logger.error(error_msg)
170
180
  return False, error_msg
171
181