AstrBot 3.5.6__py3-none-any.whl → 4.7.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 (288) hide show
  1. astrbot/api/__init__.py +16 -4
  2. astrbot/api/all.py +2 -1
  3. astrbot/api/event/__init__.py +5 -6
  4. astrbot/api/event/filter/__init__.py +37 -34
  5. astrbot/api/platform/__init__.py +7 -8
  6. astrbot/api/provider/__init__.py +8 -7
  7. astrbot/api/star/__init__.py +3 -4
  8. astrbot/api/util/__init__.py +2 -2
  9. astrbot/cli/__init__.py +1 -0
  10. astrbot/cli/__main__.py +18 -197
  11. astrbot/cli/commands/__init__.py +6 -0
  12. astrbot/cli/commands/cmd_conf.py +209 -0
  13. astrbot/cli/commands/cmd_init.py +56 -0
  14. astrbot/cli/commands/cmd_plug.py +245 -0
  15. astrbot/cli/commands/cmd_run.py +62 -0
  16. astrbot/cli/utils/__init__.py +18 -0
  17. astrbot/cli/utils/basic.py +76 -0
  18. astrbot/cli/utils/plugin.py +246 -0
  19. astrbot/cli/utils/version_comparator.py +90 -0
  20. astrbot/core/__init__.py +17 -19
  21. astrbot/core/agent/agent.py +14 -0
  22. astrbot/core/agent/handoff.py +38 -0
  23. astrbot/core/agent/hooks.py +30 -0
  24. astrbot/core/agent/mcp_client.py +385 -0
  25. astrbot/core/agent/message.py +175 -0
  26. astrbot/core/agent/response.py +14 -0
  27. astrbot/core/agent/run_context.py +22 -0
  28. astrbot/core/agent/runners/__init__.py +3 -0
  29. astrbot/core/agent/runners/base.py +65 -0
  30. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  31. astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
  32. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  33. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  34. astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
  35. astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
  36. astrbot/core/agent/tool.py +285 -0
  37. astrbot/core/agent/tool_executor.py +17 -0
  38. astrbot/core/astr_agent_context.py +19 -0
  39. astrbot/core/astr_agent_hooks.py +36 -0
  40. astrbot/core/astr_agent_run_util.py +80 -0
  41. astrbot/core/astr_agent_tool_exec.py +246 -0
  42. astrbot/core/astrbot_config_mgr.py +275 -0
  43. astrbot/core/config/__init__.py +2 -2
  44. astrbot/core/config/astrbot_config.py +60 -20
  45. astrbot/core/config/default.py +1972 -453
  46. astrbot/core/config/i18n_utils.py +110 -0
  47. astrbot/core/conversation_mgr.py +285 -75
  48. astrbot/core/core_lifecycle.py +167 -62
  49. astrbot/core/db/__init__.py +305 -102
  50. astrbot/core/db/migration/helper.py +69 -0
  51. astrbot/core/db/migration/migra_3_to_4.py +357 -0
  52. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  53. astrbot/core/db/migration/migra_webchat_session.py +131 -0
  54. astrbot/core/db/migration/shared_preferences_v3.py +48 -0
  55. astrbot/core/db/migration/sqlite_v3.py +497 -0
  56. astrbot/core/db/po.py +259 -55
  57. astrbot/core/db/sqlite.py +773 -528
  58. astrbot/core/db/vec_db/base.py +73 -0
  59. astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
  60. astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
  61. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
  62. astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
  63. astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
  64. astrbot/core/event_bus.py +26 -22
  65. astrbot/core/exceptions.py +9 -0
  66. astrbot/core/file_token_service.py +98 -0
  67. astrbot/core/initial_loader.py +19 -10
  68. astrbot/core/knowledge_base/chunking/__init__.py +9 -0
  69. astrbot/core/knowledge_base/chunking/base.py +25 -0
  70. astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
  71. astrbot/core/knowledge_base/chunking/recursive.py +161 -0
  72. astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
  73. astrbot/core/knowledge_base/kb_helper.py +642 -0
  74. astrbot/core/knowledge_base/kb_mgr.py +330 -0
  75. astrbot/core/knowledge_base/models.py +120 -0
  76. astrbot/core/knowledge_base/parsers/__init__.py +13 -0
  77. astrbot/core/knowledge_base/parsers/base.py +51 -0
  78. astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
  79. astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
  80. astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
  81. astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
  82. astrbot/core/knowledge_base/parsers/util.py +13 -0
  83. astrbot/core/knowledge_base/prompts.py +65 -0
  84. astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
  85. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  86. astrbot/core/knowledge_base/retrieval/manager.py +276 -0
  87. astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
  88. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
  89. astrbot/core/log.py +21 -15
  90. astrbot/core/message/components.py +413 -287
  91. astrbot/core/message/message_event_result.py +35 -24
  92. astrbot/core/persona_mgr.py +192 -0
  93. astrbot/core/pipeline/__init__.py +14 -14
  94. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  95. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  96. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
  97. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
  98. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  99. astrbot/core/pipeline/context.py +7 -1
  100. astrbot/core/pipeline/context_utils.py +107 -0
  101. astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
  102. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  103. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
  104. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  105. astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
  106. astrbot/core/pipeline/process_stage/stage.py +21 -15
  107. astrbot/core/pipeline/process_stage/utils.py +125 -0
  108. astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
  109. astrbot/core/pipeline/respond/stage.py +142 -101
  110. astrbot/core/pipeline/result_decorate/stage.py +124 -57
  111. astrbot/core/pipeline/scheduler.py +21 -16
  112. astrbot/core/pipeline/session_status_check/stage.py +37 -0
  113. astrbot/core/pipeline/stage.py +11 -76
  114. astrbot/core/pipeline/waking_check/stage.py +69 -33
  115. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  116. astrbot/core/platform/__init__.py +6 -6
  117. astrbot/core/platform/astr_message_event.py +107 -129
  118. astrbot/core/platform/astrbot_message.py +32 -12
  119. astrbot/core/platform/manager.py +62 -18
  120. astrbot/core/platform/message_session.py +30 -0
  121. astrbot/core/platform/platform.py +16 -24
  122. astrbot/core/platform/platform_metadata.py +9 -4
  123. astrbot/core/platform/register.py +12 -7
  124. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
  125. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
  126. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
  127. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
  128. astrbot/core/platform/sources/discord/client.py +129 -0
  129. astrbot/core/platform/sources/discord/components.py +139 -0
  130. astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
  131. astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
  132. astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
  133. astrbot/core/platform/sources/lark/lark_event.py +39 -13
  134. astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
  135. astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
  136. astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
  137. astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
  138. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
  139. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  140. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  141. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  142. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
  143. astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
  144. astrbot/core/platform/sources/satori/satori_event.py +432 -0
  145. astrbot/core/platform/sources/slack/client.py +164 -0
  146. astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
  147. astrbot/core/platform/sources/slack/slack_event.py +253 -0
  148. astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
  149. astrbot/core/platform/sources/telegram/tg_event.py +136 -36
  150. astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
  151. astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
  152. astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
  153. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
  154. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
  155. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
  156. astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
  157. astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
  158. astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
  159. astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
  160. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
  161. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
  162. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
  163. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
  164. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
  165. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
  166. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
  167. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
  168. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
  169. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
  170. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
  171. astrbot/core/platform_message_history_mgr.py +49 -0
  172. astrbot/core/provider/__init__.py +2 -3
  173. astrbot/core/provider/entites.py +8 -8
  174. astrbot/core/provider/entities.py +154 -98
  175. astrbot/core/provider/func_tool_manager.py +446 -458
  176. astrbot/core/provider/manager.py +345 -207
  177. astrbot/core/provider/provider.py +188 -73
  178. astrbot/core/provider/register.py +9 -7
  179. astrbot/core/provider/sources/anthropic_source.py +295 -115
  180. astrbot/core/provider/sources/azure_tts_source.py +224 -0
  181. astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
  182. astrbot/core/provider/sources/dashscope_tts.py +138 -14
  183. astrbot/core/provider/sources/edge_tts_source.py +24 -19
  184. astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
  185. astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
  186. astrbot/core/provider/sources/gemini_source.py +310 -132
  187. astrbot/core/provider/sources/gemini_tts_source.py +81 -0
  188. astrbot/core/provider/sources/groq_source.py +15 -0
  189. astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
  190. astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
  191. astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
  192. astrbot/core/provider/sources/openai_embedding_source.py +40 -0
  193. astrbot/core/provider/sources/openai_source.py +241 -145
  194. astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
  195. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  196. astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
  197. astrbot/core/provider/sources/volcengine_tts.py +115 -0
  198. astrbot/core/provider/sources/whisper_api_source.py +18 -13
  199. astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
  200. astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
  201. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  202. astrbot/core/provider/sources/zhipu_source.py +6 -73
  203. astrbot/core/star/__init__.py +43 -11
  204. astrbot/core/star/config.py +17 -18
  205. astrbot/core/star/context.py +362 -138
  206. astrbot/core/star/filter/__init__.py +4 -3
  207. astrbot/core/star/filter/command.py +111 -35
  208. astrbot/core/star/filter/command_group.py +46 -34
  209. astrbot/core/star/filter/custom_filter.py +6 -5
  210. astrbot/core/star/filter/event_message_type.py +4 -2
  211. astrbot/core/star/filter/permission.py +4 -2
  212. astrbot/core/star/filter/platform_adapter_type.py +45 -12
  213. astrbot/core/star/filter/regex.py +4 -2
  214. astrbot/core/star/register/__init__.py +19 -15
  215. astrbot/core/star/register/star.py +41 -13
  216. astrbot/core/star/register/star_handler.py +236 -86
  217. astrbot/core/star/session_llm_manager.py +280 -0
  218. astrbot/core/star/session_plugin_manager.py +170 -0
  219. astrbot/core/star/star.py +36 -43
  220. astrbot/core/star/star_handler.py +47 -85
  221. astrbot/core/star/star_manager.py +442 -260
  222. astrbot/core/star/star_tools.py +167 -45
  223. astrbot/core/star/updator.py +17 -20
  224. astrbot/core/umop_config_router.py +106 -0
  225. astrbot/core/updator.py +38 -13
  226. astrbot/core/utils/astrbot_path.py +39 -0
  227. astrbot/core/utils/command_parser.py +1 -1
  228. astrbot/core/utils/io.py +119 -60
  229. astrbot/core/utils/log_pipe.py +1 -1
  230. astrbot/core/utils/metrics.py +11 -10
  231. astrbot/core/utils/migra_helper.py +73 -0
  232. astrbot/core/utils/path_util.py +63 -62
  233. astrbot/core/utils/pip_installer.py +37 -15
  234. astrbot/core/utils/session_lock.py +29 -0
  235. astrbot/core/utils/session_waiter.py +19 -20
  236. astrbot/core/utils/shared_preferences.py +174 -34
  237. astrbot/core/utils/t2i/__init__.py +4 -1
  238. astrbot/core/utils/t2i/local_strategy.py +386 -238
  239. astrbot/core/utils/t2i/network_strategy.py +109 -49
  240. astrbot/core/utils/t2i/renderer.py +29 -14
  241. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  242. astrbot/core/utils/t2i/template_manager.py +111 -0
  243. astrbot/core/utils/tencent_record_helper.py +115 -1
  244. astrbot/core/utils/version_comparator.py +10 -13
  245. astrbot/core/zip_updator.py +112 -65
  246. astrbot/dashboard/routes/__init__.py +20 -13
  247. astrbot/dashboard/routes/auth.py +20 -9
  248. astrbot/dashboard/routes/chat.py +297 -141
  249. astrbot/dashboard/routes/config.py +652 -55
  250. astrbot/dashboard/routes/conversation.py +107 -37
  251. astrbot/dashboard/routes/file.py +26 -0
  252. astrbot/dashboard/routes/knowledge_base.py +1244 -0
  253. astrbot/dashboard/routes/log.py +27 -2
  254. astrbot/dashboard/routes/persona.py +202 -0
  255. astrbot/dashboard/routes/plugin.py +197 -139
  256. astrbot/dashboard/routes/route.py +27 -7
  257. astrbot/dashboard/routes/session_management.py +354 -0
  258. astrbot/dashboard/routes/stat.py +85 -18
  259. astrbot/dashboard/routes/static_file.py +5 -2
  260. astrbot/dashboard/routes/t2i.py +233 -0
  261. astrbot/dashboard/routes/tools.py +184 -120
  262. astrbot/dashboard/routes/update.py +59 -36
  263. astrbot/dashboard/server.py +96 -36
  264. astrbot/dashboard/utils.py +165 -0
  265. astrbot-4.7.0.dist-info/METADATA +294 -0
  266. astrbot-4.7.0.dist-info/RECORD +274 -0
  267. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
  268. astrbot/core/db/plugin/sqlite_impl.py +0 -112
  269. astrbot/core/db/sqlite_init.sql +0 -50
  270. astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
  271. astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
  272. astrbot/core/platform/sources/gewechat/client.py +0 -806
  273. astrbot/core/platform/sources/gewechat/downloader.py +0 -55
  274. astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
  275. astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
  276. astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
  277. astrbot/core/provider/sources/dashscope_source.py +0 -203
  278. astrbot/core/provider/sources/dify_source.py +0 -281
  279. astrbot/core/provider/sources/llmtuner_source.py +0 -132
  280. astrbot/core/rag/embedding/openai_source.py +0 -20
  281. astrbot/core/rag/knowledge_db_mgr.py +0 -94
  282. astrbot/core/rag/store/__init__.py +0 -9
  283. astrbot/core/rag/store/chroma_db.py +0 -42
  284. astrbot/core/utils/dify_api_client.py +0 -152
  285. astrbot-3.5.6.dist-info/METADATA +0 -249
  286. astrbot-3.5.6.dist-info/RECORD +0 -158
  287. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
  288. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,417 @@
1
+ """企业微信智能机器人 API 客户端
2
+ 处理消息加密解密、API 调用等
3
+ """
4
+
5
+ import base64
6
+ import hashlib
7
+ import json
8
+ from typing import Any
9
+
10
+ import aiohttp
11
+ from Crypto.Cipher import AES
12
+
13
+ from astrbot import logger
14
+
15
+ from .wecomai_utils import WecomAIBotConstants
16
+ from .WXBizJsonMsgCrypt import WXBizJsonMsgCrypt
17
+
18
+
19
+ class WecomAIBotAPIClient:
20
+ """企业微信智能机器人 API 客户端"""
21
+
22
+ def __init__(self, token: str, encoding_aes_key: str):
23
+ """初始化 API 客户端
24
+
25
+ Args:
26
+ token: 企业微信机器人 Token
27
+ encoding_aes_key: 消息加密密钥
28
+
29
+ """
30
+ self.token = token
31
+ self.encoding_aes_key = encoding_aes_key
32
+ self.wxcpt = WXBizJsonMsgCrypt(token, encoding_aes_key, "") # receiveid 为空串
33
+
34
+ async def decrypt_message(
35
+ self,
36
+ encrypted_data: bytes,
37
+ msg_signature: str,
38
+ timestamp: str,
39
+ nonce: str,
40
+ ) -> tuple[int, dict[str, Any] | None]:
41
+ """解密企业微信消息
42
+
43
+ Args:
44
+ encrypted_data: 加密的消息数据
45
+ msg_signature: 消息签名
46
+ timestamp: 时间戳
47
+ nonce: 随机数
48
+
49
+ Returns:
50
+ (错误码, 解密后的消息数据字典)
51
+
52
+ """
53
+ try:
54
+ ret, decrypted_msg = self.wxcpt.DecryptMsg(
55
+ encrypted_data,
56
+ msg_signature,
57
+ timestamp,
58
+ nonce,
59
+ )
60
+
61
+ if ret != WecomAIBotConstants.SUCCESS:
62
+ logger.error(f"消息解密失败,错误码: {ret}")
63
+ return ret, None
64
+
65
+ # 解析 JSON
66
+ if decrypted_msg:
67
+ try:
68
+ message_data = json.loads(decrypted_msg)
69
+ logger.debug(f"解密成功,消息内容: {message_data}")
70
+ return WecomAIBotConstants.SUCCESS, message_data
71
+ except json.JSONDecodeError as e:
72
+ logger.error(f"JSON 解析失败: {e}, 原始消息: {decrypted_msg}")
73
+ return WecomAIBotConstants.PARSE_XML_ERROR, None
74
+ else:
75
+ logger.error("解密消息为空")
76
+ return WecomAIBotConstants.DECRYPT_ERROR, None
77
+
78
+ except Exception as e:
79
+ logger.error(f"解密过程发生异常: {e}")
80
+ return WecomAIBotConstants.DECRYPT_ERROR, None
81
+
82
+ async def encrypt_message(
83
+ self,
84
+ plain_message: str,
85
+ nonce: str,
86
+ timestamp: str,
87
+ ) -> str | None:
88
+ """加密消息
89
+
90
+ Args:
91
+ plain_message: 明文消息
92
+ nonce: 随机数
93
+ timestamp: 时间戳
94
+
95
+ Returns:
96
+ 加密后的消息,失败时返回 None
97
+
98
+ """
99
+ try:
100
+ ret, encrypted_msg = self.wxcpt.EncryptMsg(plain_message, nonce, timestamp)
101
+
102
+ if ret != WecomAIBotConstants.SUCCESS:
103
+ logger.error(f"消息加密失败,错误码: {ret}")
104
+ return None
105
+
106
+ logger.debug("消息加密成功")
107
+ return encrypted_msg
108
+
109
+ except Exception as e:
110
+ logger.error(f"加密过程发生异常: {e}")
111
+ return None
112
+
113
+ def verify_url(
114
+ self,
115
+ msg_signature: str,
116
+ timestamp: str,
117
+ nonce: str,
118
+ echostr: str,
119
+ ) -> str:
120
+ """验证回调 URL
121
+
122
+ Args:
123
+ msg_signature: 消息签名
124
+ timestamp: 时间戳
125
+ nonce: 随机数
126
+ echostr: 验证字符串
127
+
128
+ Returns:
129
+ 验证结果字符串
130
+
131
+ """
132
+ try:
133
+ ret, echo_result = self.wxcpt.VerifyURL(
134
+ msg_signature,
135
+ timestamp,
136
+ nonce,
137
+ echostr,
138
+ )
139
+
140
+ if ret != WecomAIBotConstants.SUCCESS:
141
+ logger.error(f"URL 验证失败,错误码: {ret}")
142
+ return "verify fail"
143
+
144
+ logger.info("URL 验证成功")
145
+ return echo_result if echo_result else "verify fail"
146
+
147
+ except Exception as e:
148
+ logger.error(f"URL 验证发生异常: {e}")
149
+ return "verify fail"
150
+
151
+ async def process_encrypted_image(
152
+ self,
153
+ image_url: str,
154
+ aes_key_base64: str | None = None,
155
+ ) -> tuple[bool, bytes | str]:
156
+ """下载并解密加密图片
157
+
158
+ Args:
159
+ image_url: 加密图片的 URL
160
+ aes_key_base64: Base64 编码的 AES 密钥,如果为 None 则使用实例的密钥
161
+
162
+ Returns:
163
+ (是否成功, 图片数据或错误信息)
164
+
165
+ """
166
+ try:
167
+ # 下载图片
168
+ logger.info(f"开始下载加密图片: {image_url}")
169
+
170
+ async with aiohttp.ClientSession() as session:
171
+ async with session.get(image_url, timeout=15) as response:
172
+ if response.status != 200:
173
+ error_msg = f"图片下载失败,状态码: {response.status}"
174
+ logger.error(error_msg)
175
+ return False, error_msg
176
+
177
+ encrypted_data = await response.read()
178
+ logger.info(f"图片下载成功,大小: {len(encrypted_data)} 字节")
179
+
180
+ # 准备解密密钥
181
+ if aes_key_base64 is None:
182
+ aes_key_base64 = self.encoding_aes_key
183
+
184
+ if not aes_key_base64:
185
+ raise ValueError("AES 密钥不能为空")
186
+
187
+ # Base64 解码密钥
188
+ aes_key = base64.b64decode(
189
+ aes_key_base64 + "=" * (-len(aes_key_base64) % 4),
190
+ )
191
+ if len(aes_key) != 32:
192
+ raise ValueError("无效的 AES 密钥长度: 应为 32 字节")
193
+
194
+ iv = aes_key[:16] # 初始向量为密钥前 16 字节
195
+
196
+ # 解密图片数据
197
+ cipher = AES.new(aes_key, AES.MODE_CBC, iv)
198
+ decrypted_data = cipher.decrypt(encrypted_data)
199
+
200
+ # 去除 PKCS#7 填充
201
+ pad_len = decrypted_data[-1]
202
+ if pad_len > 32: # AES-256 块大小为 32 字节
203
+ raise ValueError("无效的填充长度 (大于32字节)")
204
+
205
+ decrypted_data = decrypted_data[:-pad_len]
206
+ logger.info(f"图片解密成功,解密后大小: {len(decrypted_data)} 字节")
207
+
208
+ return True, decrypted_data
209
+
210
+ except aiohttp.ClientError as e:
211
+ error_msg = f"图片下载失败: {e!s}"
212
+ logger.error(error_msg)
213
+ return False, error_msg
214
+
215
+ except ValueError as e:
216
+ error_msg = f"参数错误: {e!s}"
217
+ logger.error(error_msg)
218
+ return False, error_msg
219
+
220
+ except Exception as e:
221
+ error_msg = f"图片处理异常: {e!s}"
222
+ logger.error(error_msg)
223
+ return False, error_msg
224
+
225
+
226
+ class WecomAIBotStreamMessageBuilder:
227
+ """企业微信智能机器人流消息构建器"""
228
+
229
+ @staticmethod
230
+ def make_text_stream(stream_id: str, content: str, finish: bool = False) -> str:
231
+ """构建文本流消息
232
+
233
+ Args:
234
+ stream_id: 流 ID
235
+ content: 文本内容
236
+ finish: 是否结束
237
+
238
+ Returns:
239
+ JSON 格式的流消息字符串
240
+
241
+ """
242
+ plain = {
243
+ "msgtype": WecomAIBotConstants.MSG_TYPE_STREAM,
244
+ "stream": {"id": stream_id, "finish": finish, "content": content},
245
+ }
246
+ return json.dumps(plain, ensure_ascii=False)
247
+
248
+ @staticmethod
249
+ def make_image_stream(
250
+ stream_id: str,
251
+ image_data: bytes,
252
+ finish: bool = False,
253
+ ) -> str:
254
+ """构建图片流消息
255
+
256
+ Args:
257
+ stream_id: 流 ID
258
+ image_data: 图片二进制数据
259
+ finish: 是否结束
260
+
261
+ Returns:
262
+ JSON 格式的流消息字符串
263
+
264
+ """
265
+ image_md5 = hashlib.md5(image_data).hexdigest()
266
+ image_base64 = base64.b64encode(image_data).decode("utf-8")
267
+
268
+ plain = {
269
+ "msgtype": WecomAIBotConstants.MSG_TYPE_STREAM,
270
+ "stream": {
271
+ "id": stream_id,
272
+ "finish": finish,
273
+ "msg_item": [
274
+ {
275
+ "msgtype": WecomAIBotConstants.MSG_TYPE_IMAGE,
276
+ "image": {"base64": image_base64, "md5": image_md5},
277
+ },
278
+ ],
279
+ },
280
+ }
281
+ return json.dumps(plain, ensure_ascii=False)
282
+
283
+ @staticmethod
284
+ def make_mixed_stream(
285
+ stream_id: str,
286
+ content: str,
287
+ msg_items: list,
288
+ finish: bool = False,
289
+ ) -> str:
290
+ """构建混合类型流消息
291
+
292
+ Args:
293
+ stream_id: 流 ID
294
+ content: 文本内容
295
+ msg_items: 消息项列表
296
+ finish: 是否结束
297
+
298
+ Returns:
299
+ JSON 格式的流消息字符串
300
+
301
+ """
302
+ plain = {
303
+ "msgtype": WecomAIBotConstants.MSG_TYPE_STREAM,
304
+ "stream": {"id": stream_id, "finish": finish, "msg_item": msg_items},
305
+ }
306
+ if content:
307
+ plain["stream"]["content"] = content
308
+ return json.dumps(plain, ensure_ascii=False)
309
+
310
+ @staticmethod
311
+ def make_text(content: str) -> str:
312
+ """构建文本消息
313
+
314
+ Args:
315
+ content: 文本内容
316
+
317
+ Returns:
318
+ JSON 格式的文本消息字符串
319
+
320
+ """
321
+ plain = {"msgtype": "text", "text": {"content": content}}
322
+ return json.dumps(plain, ensure_ascii=False)
323
+
324
+
325
+ class WecomAIBotMessageParser:
326
+ """企业微信智能机器人消息解析器"""
327
+
328
+ @staticmethod
329
+ def parse_text_message(data: dict[str, Any]) -> str | None:
330
+ """解析文本消息
331
+
332
+ Args:
333
+ data: 消息数据
334
+
335
+ Returns:
336
+ 文本内容,解析失败返回 None
337
+
338
+ """
339
+ try:
340
+ return data.get("text", {}).get("content")
341
+ except (KeyError, TypeError):
342
+ logger.warning("文本消息解析失败")
343
+ return None
344
+
345
+ @staticmethod
346
+ def parse_image_message(data: dict[str, Any]) -> str | None:
347
+ """解析图片消息
348
+
349
+ Args:
350
+ data: 消息数据
351
+
352
+ Returns:
353
+ 图片 URL,解析失败返回 None
354
+
355
+ """
356
+ try:
357
+ return data.get("image", {}).get("url")
358
+ except (KeyError, TypeError):
359
+ logger.warning("图片消息解析失败")
360
+ return None
361
+
362
+ @staticmethod
363
+ def parse_stream_message(data: dict[str, Any]) -> dict[str, Any] | None:
364
+ """解析流消息
365
+
366
+ Args:
367
+ data: 消息数据
368
+
369
+ Returns:
370
+ 流消息数据,解析失败返回 None
371
+
372
+ """
373
+ try:
374
+ stream_data = data.get("stream", {})
375
+ return {
376
+ "id": stream_data.get("id"),
377
+ "finish": stream_data.get("finish"),
378
+ "content": stream_data.get("content"),
379
+ "msg_item": stream_data.get("msg_item", []),
380
+ }
381
+ except (KeyError, TypeError):
382
+ logger.warning("流消息解析失败")
383
+ return None
384
+
385
+ @staticmethod
386
+ def parse_mixed_message(data: dict[str, Any]) -> list | None:
387
+ """解析混合消息
388
+
389
+ Args:
390
+ data: 消息数据
391
+
392
+ Returns:
393
+ 消息项列表,解析失败返回 None
394
+
395
+ """
396
+ try:
397
+ return data.get("mixed", {}).get("msg_item", [])
398
+ except (KeyError, TypeError):
399
+ logger.warning("混合消息解析失败")
400
+ return None
401
+
402
+ @staticmethod
403
+ def parse_event_message(data: dict[str, Any]) -> dict[str, Any] | None:
404
+ """解析事件消息
405
+
406
+ Args:
407
+ data: 消息数据
408
+
409
+ Returns:
410
+ 事件数据,解析失败返回 None
411
+
412
+ """
413
+ try:
414
+ return data.get("event", {})
415
+ except (KeyError, TypeError):
416
+ logger.warning("事件消息解析失败")
417
+ return None
@@ -0,0 +1,152 @@
1
+ """企业微信智能机器人事件处理模块,处理消息事件的发送和接收"""
2
+
3
+ from astrbot.api import logger
4
+ from astrbot.api.event import AstrMessageEvent, MessageChain
5
+ from astrbot.api.message_components import (
6
+ Image,
7
+ Plain,
8
+ )
9
+
10
+ from .wecomai_api import WecomAIBotAPIClient
11
+ from .wecomai_queue_mgr import WecomAIQueueMgr
12
+
13
+
14
+ class WecomAIBotMessageEvent(AstrMessageEvent):
15
+ """企业微信智能机器人消息事件"""
16
+
17
+ def __init__(
18
+ self,
19
+ message_str: str,
20
+ message_obj,
21
+ platform_meta,
22
+ session_id: str,
23
+ api_client: WecomAIBotAPIClient,
24
+ queue_mgr: WecomAIQueueMgr,
25
+ ):
26
+ """初始化消息事件
27
+
28
+ Args:
29
+ message_str: 消息字符串
30
+ message_obj: 消息对象
31
+ platform_meta: 平台元数据
32
+ session_id: 会话 ID
33
+ api_client: API 客户端
34
+
35
+ """
36
+ super().__init__(message_str, message_obj, platform_meta, session_id)
37
+ self.api_client = api_client
38
+ self.queue_mgr = queue_mgr
39
+
40
+ @staticmethod
41
+ async def _send(
42
+ message_chain: MessageChain,
43
+ stream_id: str,
44
+ queue_mgr: WecomAIQueueMgr,
45
+ streaming: bool = False,
46
+ ):
47
+ back_queue = queue_mgr.get_or_create_back_queue(stream_id)
48
+
49
+ if not message_chain:
50
+ await back_queue.put(
51
+ {
52
+ "type": "end",
53
+ "data": "",
54
+ "streaming": False,
55
+ },
56
+ )
57
+ return ""
58
+
59
+ data = ""
60
+ for comp in message_chain.chain:
61
+ if isinstance(comp, Plain):
62
+ data = comp.text
63
+ await back_queue.put(
64
+ {
65
+ "type": "plain",
66
+ "data": data,
67
+ "streaming": streaming,
68
+ "session_id": stream_id,
69
+ },
70
+ )
71
+ elif isinstance(comp, Image):
72
+ # 处理图片消息
73
+ try:
74
+ image_base64 = await comp.convert_to_base64()
75
+ if image_base64:
76
+ await back_queue.put(
77
+ {
78
+ "type": "image",
79
+ "image_data": image_base64,
80
+ "streaming": streaming,
81
+ "session_id": stream_id,
82
+ },
83
+ )
84
+ else:
85
+ logger.warning("图片数据为空,跳过")
86
+ except Exception as e:
87
+ logger.error("处理图片消息失败: %s", e)
88
+ else:
89
+ logger.warning(f"[WecomAI] 不支持的消息组件类型: {type(comp)}, 跳过")
90
+
91
+ return data
92
+
93
+ async def send(self, message: MessageChain):
94
+ """发送消息"""
95
+ raw = self.message_obj.raw_message
96
+ assert isinstance(raw, dict), (
97
+ "wecom_ai_bot platform event raw_message should be a dict"
98
+ )
99
+ stream_id = raw.get("stream_id", self.session_id)
100
+ await WecomAIBotMessageEvent._send(message, stream_id, self.queue_mgr)
101
+ await super().send(message)
102
+
103
+ async def send_streaming(self, generator, use_fallback=False):
104
+ """流式发送消息,参考webchat的send_streaming设计"""
105
+ final_data = ""
106
+ raw = self.message_obj.raw_message
107
+ assert isinstance(raw, dict), (
108
+ "wecom_ai_bot platform event raw_message should be a dict"
109
+ )
110
+ stream_id = raw.get("stream_id", self.session_id)
111
+ back_queue = self.queue_mgr.get_or_create_back_queue(stream_id)
112
+
113
+ # 企业微信智能机器人不支持增量发送,因此我们需要在这里将增量内容累积起来,积累发送
114
+ increment_plain = ""
115
+ async for chain in generator:
116
+ # 累积增量内容,并改写 Plain 段
117
+ chain.squash_plain()
118
+ for comp in chain.chain:
119
+ if isinstance(comp, Plain):
120
+ comp.text = increment_plain + comp.text
121
+ increment_plain = comp.text
122
+ break
123
+
124
+ if chain.type == "break" and final_data:
125
+ # 分割符
126
+ await back_queue.put(
127
+ {
128
+ "type": "break", # break means a segment end
129
+ "data": final_data,
130
+ "streaming": True,
131
+ "session_id": self.session_id,
132
+ },
133
+ )
134
+ final_data = ""
135
+ continue
136
+
137
+ final_data += await WecomAIBotMessageEvent._send(
138
+ chain,
139
+ stream_id=stream_id,
140
+ queue_mgr=self.queue_mgr,
141
+ streaming=True,
142
+ )
143
+
144
+ await back_queue.put(
145
+ {
146
+ "type": "complete", # complete means we return the final result
147
+ "data": final_data,
148
+ "streaming": True,
149
+ "session_id": self.session_id,
150
+ },
151
+ )
152
+ await super().send_streaming(generator, use_fallback)