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
@@ -1,33 +1,42 @@
1
- import time
2
1
  import asyncio
2
+ import itertools
3
3
  import logging
4
+ import time
4
5
  import uuid
5
- import itertools
6
- from typing import Awaitable, Any
6
+ from collections.abc import Awaitable
7
+ from typing import Any
8
+
7
9
  from aiocqhttp import CQHttp, Event
10
+ from aiocqhttp.exceptions import ActionFailed
11
+
12
+ from astrbot.api import logger
13
+ from astrbot.api.event import MessageChain
14
+ from astrbot.api.message_components import *
8
15
  from astrbot.api.platform import (
9
- Platform,
10
16
  AstrBotMessage,
11
17
  MessageMember,
12
18
  MessageType,
19
+ Platform,
13
20
  PlatformMetadata,
14
21
  )
15
- from astrbot.api.event import MessageChain
16
- from .aiocqhttp_message_event import * # noqa: F403
17
- from astrbot.api.message_components import * # noqa: F403
18
- from astrbot.api import logger
19
- from .aiocqhttp_message_event import AiocqhttpMessageEvent
20
22
  from astrbot.core.platform.astr_message_event import MessageSesion
23
+
21
24
  from ...register import register_platform_adapter
22
- from aiocqhttp.exceptions import ActionFailed
25
+ from .aiocqhttp_message_event import *
26
+ from .aiocqhttp_message_event import AiocqhttpMessageEvent
23
27
 
24
28
 
25
29
  @register_platform_adapter(
26
- "aiocqhttp", "适用于 OneBot V11 标准的消息平台适配器,支持反向 WebSockets。"
30
+ "aiocqhttp",
31
+ "适用于 OneBot V11 标准的消息平台适配器,支持反向 WebSockets。",
32
+ support_streaming_message=False,
27
33
  )
28
34
  class AiocqhttpAdapter(Platform):
29
35
  def __init__(
30
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
36
+ self,
37
+ platform_config: dict,
38
+ platform_settings: dict,
39
+ event_queue: asyncio.Queue,
31
40
  ) -> None:
32
41
  super().__init__(event_queue)
33
42
 
@@ -41,6 +50,7 @@ class AiocqhttpAdapter(Platform):
41
50
  name="aiocqhttp",
42
51
  description="适用于 OneBot 标准的消息平台适配器,支持反向 WebSockets。",
43
52
  id=self.config.get("id"),
53
+ support_streaming_message=False,
44
54
  )
45
55
 
46
56
  self.bot = CQHttp(
@@ -48,7 +58,7 @@ class AiocqhttpAdapter(Platform):
48
58
  import_name="aiocqhttp",
49
59
  api_timeout_sec=180,
50
60
  access_token=platform_config.get(
51
- "ws_reverse_token"
61
+ "ws_reverse_token",
52
62
  ), # 以防旧版本配置不存在
53
63
  )
54
64
 
@@ -81,28 +91,32 @@ class AiocqhttpAdapter(Platform):
81
91
  logger.info("aiocqhttp(OneBot v11) 适配器已连接。")
82
92
 
83
93
  async def send_by_session(
84
- self, session: MessageSesion, message_chain: MessageChain
94
+ self,
95
+ session: MessageSesion,
96
+ message_chain: MessageChain,
85
97
  ):
86
- ret = await AiocqhttpMessageEvent._parse_onebot_json(message_chain)
87
- match session.message_type.value:
88
- case MessageType.GROUP_MESSAGE.value:
89
- if "_" in session.session_id:
90
- # 独立会话
91
- _, group_id = session.session_id.split("_")
92
- await self.bot.send_group_msg(group_id=group_id, message=ret)
93
- else:
94
- await self.bot.send_group_msg(
95
- group_id=session.session_id, message=ret
96
- )
97
- case MessageType.FRIEND_MESSAGE.value:
98
- await self.bot.send_private_msg(user_id=session.session_id, message=ret)
98
+ is_group = session.message_type == MessageType.GROUP_MESSAGE
99
+ if is_group:
100
+ session_id = session.session_id.split("_")[-1]
101
+ else:
102
+ session_id = session.session_id
103
+ await AiocqhttpMessageEvent.send_message(
104
+ bot=self.bot,
105
+ message_chain=message_chain,
106
+ event=None, # 这里不需要 event,因为是通过 session 发送的
107
+ is_group=is_group,
108
+ session_id=session_id,
109
+ )
99
110
  await super().send_by_session(session, message_chain)
100
111
 
101
- async def convert_message(self, event: Event) -> AstrBotMessage:
112
+ async def convert_message(self, event: Event) -> AstrBotMessage | None:
102
113
  logger.debug(f"[aiocqhttp] RawMessage {event}")
103
114
 
104
115
  if event["post_type"] == "message":
105
116
  abm = await self._convert_handle_message_event(event)
117
+ if abm.sender.user_id == "2854196310":
118
+ # 屏蔽 QQ 管家的消息
119
+ return None
106
120
  elif event["post_type"] == "notice":
107
121
  abm = await self._convert_handle_notice_event(event)
108
122
  elif event["post_type"] == "request":
@@ -116,7 +130,7 @@ class AiocqhttpAdapter(Platform):
116
130
  abm.self_id = str(event.self_id)
117
131
  abm.sender = MessageMember(user_id=str(event.user_id), nickname=event.user_id)
118
132
  abm.type = MessageType.OTHER_MESSAGE
119
- if "group_id" in event and event["group_id"]:
133
+ if event.get("group_id"):
120
134
  abm.type = MessageType.GROUP_MESSAGE
121
135
  abm.group_id = str(event.group_id)
122
136
  else:
@@ -142,7 +156,7 @@ class AiocqhttpAdapter(Platform):
142
156
  abm.self_id = str(event.self_id)
143
157
  abm.sender = MessageMember(user_id=str(event.user_id), nickname=event.user_id)
144
158
  abm.type = MessageType.OTHER_MESSAGE
145
- if "group_id" in event and event["group_id"]:
159
+ if event.get("group_id"):
146
160
  abm.group_id = str(event.group_id)
147
161
  abm.type = MessageType.GROUP_MESSAGE
148
162
  else:
@@ -165,14 +179,14 @@ class AiocqhttpAdapter(Platform):
165
179
 
166
180
  if "sub_type" in event:
167
181
  if event["sub_type"] == "poke" and "target_id" in event:
168
- abm.message.append(
169
- Poke(qq=str(event["target_id"]), type="poke")
170
- ) # noqa: F405
182
+ abm.message.append(Poke(qq=str(event["target_id"]), type="poke"))
171
183
 
172
184
  return abm
173
185
 
174
186
  async def _convert_handle_message_event(
175
- self, event: Event, get_reply=True
187
+ self,
188
+ event: Event,
189
+ get_reply=True,
176
190
  ) -> AstrBotMessage:
177
191
  """OneBot V11 消息类事件
178
192
 
@@ -182,11 +196,13 @@ class AiocqhttpAdapter(Platform):
182
196
  abm = AstrBotMessage()
183
197
  abm.self_id = str(event.self_id)
184
198
  abm.sender = MessageMember(
185
- str(event.sender["user_id"]), event.sender["nickname"]
199
+ str(event.sender["user_id"]),
200
+ event.sender.get("card") or event.sender.get("nickname", "N/A"),
186
201
  )
187
202
  if event["message_type"] == "group":
188
203
  abm.type = MessageType.GROUP_MESSAGE
189
204
  abm.group_id = str(event.group_id)
205
+ abm.group.group_name = event.get("group_name", "N/A")
190
206
  elif event["message_type"] == "private":
191
207
  abm.type = MessageType.FRIEND_MESSAGE
192
208
  if self.unique_session and abm.type == MessageType.GROUP_MESSAGE:
@@ -205,21 +221,24 @@ class AiocqhttpAdapter(Platform):
205
221
 
206
222
  message_str = ""
207
223
  if not isinstance(event.message, list):
208
- err = f"aiocqhttp: 无法识别的消息类型: {str(event.message)},此条消息将被忽略。如果您在使用 go-cqhttp,请将其配置文件中的 message.post-format 更改为 array。"
224
+ err = f"aiocqhttp: 无法识别的消息类型: {event.message!s},此条消息将被忽略。如果您在使用 go-cqhttp,请将其配置文件中的 message.post-format 更改为 array。"
209
225
  logger.critical(err)
210
226
  try:
211
- self.bot.send(event, err)
227
+ await self.bot.send(event, err)
212
228
  except BaseException as e:
213
229
  logger.error(f"回复消息失败: {e}")
214
- return
230
+ return None
215
231
 
216
232
  # 按消息段类型类型适配
217
233
  for t, m_group in itertools.groupby(event.message, key=lambda x: x["type"]):
218
234
  a = None
219
235
  if t == "text":
220
- # 合并相邻文本段
221
- message_str = "".join(m["data"]["text"] for m in m_group).strip()
222
- a = ComponentTypes[t](text=message_str) # noqa: F405
236
+ current_text = "".join(m["data"]["text"] for m in m_group).strip()
237
+ if not current_text:
238
+ # 如果文本段为空,则跳过
239
+ continue
240
+ message_str += current_text
241
+ a = ComponentTypes[t](text=current_text)
223
242
  abm.message.append(a)
224
243
 
225
244
  elif t == "file":
@@ -259,7 +278,7 @@ class AiocqhttpAdapter(Platform):
259
278
  elif t == "reply":
260
279
  for m in m_group:
261
280
  if not get_reply:
262
- a = ComponentTypes[t](**m["data"]) # noqa: F405
281
+ a = ComponentTypes[t](**m["data"])
263
282
  abm.message.append(a)
264
283
  else:
265
284
  try:
@@ -267,8 +286,17 @@ class AiocqhttpAdapter(Platform):
267
286
  action="get_msg",
268
287
  message_id=int(m["data"]["id"]),
269
288
  )
289
+ # 添加必要的 post_type 字段,防止 Event.from_payload 报错
290
+ reply_event_data["post_type"] = "message"
291
+ new_event = Event.from_payload(reply_event_data)
292
+ if not new_event:
293
+ logger.error(
294
+ f"无法从回复消息数据构造 Event 对象: {reply_event_data}",
295
+ )
296
+ continue
270
297
  abm_reply = await self._convert_handle_message_event(
271
- Event.from_payload(reply_event_data), get_reply=False
298
+ new_event,
299
+ get_reply=False,
272
300
  )
273
301
 
274
302
  reply_seg = Reply(
@@ -285,11 +313,63 @@ class AiocqhttpAdapter(Platform):
285
313
  abm.message.append(reply_seg)
286
314
  except BaseException as e:
287
315
  logger.error(f"获取引用消息失败: {e}。")
288
- a = ComponentTypes[t](**m["data"]) # noqa: F405
316
+ a = ComponentTypes[t](**m["data"])
289
317
  abm.message.append(a)
318
+ elif t == "at":
319
+ first_at_self_processed = False
320
+ # Accumulate @ mention text for efficient concatenation
321
+ at_parts = []
322
+
323
+ for m in m_group:
324
+ try:
325
+ if m["data"]["qq"] == "all":
326
+ abm.message.append(At(qq="all", name="全体成员"))
327
+ continue
328
+
329
+ at_info = await self.bot.call_action(
330
+ action="get_group_member_info",
331
+ group_id=event.group_id,
332
+ user_id=int(m["data"]["qq"]),
333
+ no_cache=False,
334
+ )
335
+ if at_info:
336
+ nickname = at_info.get("card", "")
337
+ if nickname == "":
338
+ at_info = await self.bot.call_action(
339
+ action="get_stranger_info",
340
+ user_id=int(m["data"]["qq"]),
341
+ no_cache=False,
342
+ )
343
+ nickname = at_info.get("nick", "") or at_info.get(
344
+ "nickname",
345
+ "",
346
+ )
347
+ is_at_self = str(m["data"]["qq"]) in {abm.self_id, "all"}
348
+
349
+ abm.message.append(
350
+ At(
351
+ qq=m["data"]["qq"],
352
+ name=nickname,
353
+ ),
354
+ )
355
+
356
+ if is_at_self and not first_at_self_processed:
357
+ # 第一个@是机器人,不添加到message_str
358
+ first_at_self_processed = True
359
+ else:
360
+ # 非第一个@机器人或@其他用户,添加到message_str
361
+ at_parts.append(f" @{nickname}({m['data']['qq']}) ")
362
+ else:
363
+ abm.message.append(At(qq=str(m["data"]["qq"]), name=""))
364
+ except ActionFailed as e:
365
+ logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
366
+ except BaseException as e:
367
+ logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
368
+
369
+ message_str += "".join(at_parts)
290
370
  else:
291
371
  for m in m_group:
292
- a = ComponentTypes[t](**m["data"]) # noqa: F405
372
+ a = ComponentTypes[t](**m["data"])
293
373
  abm.message.append(a)
294
374
 
295
375
  abm.timestamp = int(time.time())
@@ -301,7 +381,7 @@ class AiocqhttpAdapter(Platform):
301
381
  def run(self) -> Awaitable[Any]:
302
382
  if not self.host or not self.port:
303
383
  logger.warning(
304
- "aiocqhttp: 未配置 ws_reverse_host 或 ws_reverse_port,将使用默认值:http://0.0.0.0:6199"
384
+ "aiocqhttp: 未配置 ws_reverse_host 或 ws_reverse_port,将使用默认值:http://0.0.0.0:6199",
305
385
  )
306
386
  self.host = "0.0.0.0"
307
387
  self.port = 6199
@@ -1,25 +1,29 @@
1
1
  import asyncio
2
+ import os
3
+ import threading
2
4
  import uuid
5
+
3
6
  import aiohttp
4
7
  import dingtalk_stream
5
- import threading
8
+ from dingtalk_stream import AckMessage
6
9
 
10
+ from astrbot import logger
11
+ from astrbot.api.event import MessageChain
12
+ from astrbot.api.message_components import At, Image, Plain
7
13
  from astrbot.api.platform import (
8
- Platform,
9
14
  AstrBotMessage,
10
15
  MessageMember,
11
16
  MessageType,
17
+ Platform,
12
18
  PlatformMetadata,
13
19
  )
14
- from astrbot.api.event import MessageChain
15
- from astrbot.api.message_components import Image, Plain, At
16
20
  from astrbot.core.platform.astr_message_event import MessageSesion
17
- from .dingtalk_event import DingtalkMessageEvent
18
- from ...register import register_platform_adapter
19
- from astrbot import logger
20
- from dingtalk_stream import AckMessage
21
+ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
21
22
  from astrbot.core.utils.io import download_file
22
23
 
24
+ from ...register import register_platform_adapter
25
+ from .dingtalk_event import DingtalkMessageEvent
26
+
23
27
 
24
28
  class MyEventHandler(dingtalk_stream.EventHandler):
25
29
  async def process(self, event: dingtalk_stream.EventMessage):
@@ -33,10 +37,15 @@ class MyEventHandler(dingtalk_stream.EventHandler):
33
37
  return AckMessage.STATUS_OK, "OK"
34
38
 
35
39
 
36
- @register_platform_adapter("dingtalk", "钉钉机器人官方 API 适配器")
40
+ @register_platform_adapter(
41
+ "dingtalk", "钉钉机器人官方 API 适配器", support_streaming_message=False
42
+ )
37
43
  class DingtalkPlatformAdapter(Platform):
38
44
  def __init__(
39
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
45
+ self,
46
+ platform_config: dict,
47
+ platform_settings: dict,
48
+ event_queue: asyncio.Queue,
40
49
  ) -> None:
41
50
  super().__init__(event_queue)
42
51
 
@@ -62,12 +71,23 @@ class DingtalkPlatformAdapter(Platform):
62
71
  client = dingtalk_stream.DingTalkStreamClient(credential, logger=logger)
63
72
  client.register_all_event_handler(MyEventHandler())
64
73
  client.register_callback_handler(
65
- dingtalk_stream.ChatbotMessage.TOPIC, self.client
74
+ dingtalk_stream.ChatbotMessage.TOPIC,
75
+ self.client,
66
76
  )
67
77
  self.client_ = client # 用于 websockets 的 client
68
78
 
79
+ def _id_to_sid(self, dingtalk_id: str | None) -> str | None:
80
+ if not dingtalk_id:
81
+ return dingtalk_id
82
+ prefix = "$:LWCP_v1:$"
83
+ if dingtalk_id.startswith(prefix):
84
+ return dingtalk_id[len(prefix) :]
85
+ return dingtalk_id
86
+
69
87
  async def send_by_session(
70
- self, session: MessageSesion, message_chain: MessageChain
88
+ self,
89
+ session: MessageSesion,
90
+ message_chain: MessageChain,
71
91
  ):
72
92
  raise NotImplementedError("钉钉机器人适配器不支持 send_by_session")
73
93
 
@@ -76,10 +96,12 @@ class DingtalkPlatformAdapter(Platform):
76
96
  name="dingtalk",
77
97
  description="钉钉机器人官方 API 适配器",
78
98
  id=self.config.get("id"),
99
+ support_streaming_message=False,
79
100
  )
80
101
 
81
102
  async def convert_msg(
82
- self, message: dingtalk_stream.ChatbotMessage
103
+ self,
104
+ message: dingtalk_stream.ChatbotMessage,
83
105
  ) -> AstrBotMessage:
84
106
  abm = AstrBotMessage()
85
107
  abm.message = []
@@ -91,15 +113,19 @@ class DingtalkPlatformAdapter(Platform):
91
113
  else MessageType.FRIEND_MESSAGE
92
114
  )
93
115
  abm.sender = MessageMember(
94
- user_id=message.sender_id, nickname=message.sender_nick
116
+ user_id=self._id_to_sid(message.sender_id),
117
+ nickname=message.sender_nick,
95
118
  )
96
- abm.self_id = message.chatbot_user_id
119
+ abm.self_id = self._id_to_sid(message.chatbot_user_id)
97
120
  abm.message_id = message.message_id
98
121
  abm.raw_message = message
99
122
 
100
123
  if abm.type == MessageType.GROUP_MESSAGE:
101
- if message.is_in_at_list:
102
- abm.message.append(At(qq=abm.self_id))
124
+ # 处理所有被 @ 的用户(包括机器人自己,因 at_users 已包含)
125
+ if message.at_users:
126
+ for user in message.at_users:
127
+ if id := self._id_to_sid(user.dingtalk_id):
128
+ abm.message.append(At(qq=id))
103
129
  abm.group_id = message.conversation_id
104
130
  if self.unique_session:
105
131
  abm.session_id = abm.sender.user_id
@@ -134,7 +160,10 @@ class DingtalkPlatformAdapter(Platform):
134
160
  return abm # 别忘了返回转换后的消息对象
135
161
 
136
162
  async def download_ding_file(
137
- self, download_code: str, robot_code: str, ext: str
163
+ self,
164
+ download_code: str,
165
+ robot_code: str,
166
+ ext: str,
138
167
  ) -> str:
139
168
  """下载钉钉文件
140
169
 
@@ -152,21 +181,24 @@ class DingtalkPlatformAdapter(Platform):
152
181
  "downloadCode": download_code,
153
182
  "robotCode": robot_code,
154
183
  }
155
- f_path = f"data/dingtalk_file_{uuid.uuid4()}.{ext}"
156
- async with aiohttp.ClientSession() as session:
157
- async with session.post(
184
+ temp_dir = os.path.join(get_astrbot_data_path(), "temp")
185
+ f_path = os.path.join(temp_dir, f"dingtalk_file_{uuid.uuid4()}.{ext}")
186
+ async with (
187
+ aiohttp.ClientSession() as session,
188
+ session.post(
158
189
  "https://api.dingtalk.com/v1.0/robot/messageFiles/download",
159
190
  headers=headers,
160
191
  json=payload,
161
- ) as resp:
162
- if resp.status != 200:
163
- logger.error(
164
- f"下载钉钉文件失败: {resp.status}, {await resp.text()}"
165
- )
166
- return None
167
- resp_data = await resp.json()
168
- download_url = resp_data["data"]["downloadUrl"]
169
- await download_file(download_url, f_path)
192
+ ) as resp,
193
+ ):
194
+ if resp.status != 200:
195
+ logger.error(
196
+ f"下载钉钉文件失败: {resp.status}, {await resp.text()}",
197
+ )
198
+ return None
199
+ resp_data = await resp.json()
200
+ download_url = resp_data["data"]["downloadUrl"]
201
+ await download_file(download_url, f_path)
170
202
  return f_path
171
203
 
172
204
  async def get_access_token(self) -> str:
@@ -181,7 +213,7 @@ class DingtalkPlatformAdapter(Platform):
181
213
  ) as resp:
182
214
  if resp.status != 200:
183
215
  logger.error(
184
- f"获取钉钉机器人 access_token 失败: {resp.status}, {await resp.text()}"
216
+ f"获取钉钉机器人 access_token 失败: {resp.status}, {await resp.text()}",
185
217
  )
186
218
  return None
187
219
  return (await resp.json())["data"]["accessToken"]
@@ -1,8 +1,10 @@
1
1
  import asyncio
2
+
2
3
  import dingtalk_stream
4
+
3
5
  import astrbot.api.message_components as Comp
4
- from astrbot.api.event import AstrMessageEvent, MessageChain
5
6
  from astrbot import logger
7
+ from astrbot.api.event import AstrMessageEvent, MessageChain
6
8
 
7
9
 
8
10
  class DingtalkMessageEvent(AstrMessageEvent):
@@ -18,7 +20,9 @@ class DingtalkMessageEvent(AstrMessageEvent):
18
20
  self.client = client
19
21
 
20
22
  async def send_with_client(
21
- self, client: dingtalk_stream.ChatbotHandler, message: MessageChain
23
+ self,
24
+ client: dingtalk_stream.ChatbotHandler,
25
+ message: MessageChain,
22
26
  ):
23
27
  for segment in message.chain:
24
28
  if isinstance(segment, Comp.Plain):
@@ -26,36 +30,36 @@ class DingtalkMessageEvent(AstrMessageEvent):
26
30
  await asyncio.get_event_loop().run_in_executor(
27
31
  None,
28
32
  client.reply_markdown,
29
- "AstrBot",
33
+ segment.text,
30
34
  segment.text,
31
35
  self.message_obj.raw_message,
32
36
  )
33
37
  elif isinstance(segment, Comp.Image):
34
38
  markdown_str = ""
35
- if segment.file and segment.file.startswith("file:///"):
36
- logger.warning(
37
- "dingtalk only support url image, not: " + segment.file
38
- )
39
- continue
40
- elif segment.file and segment.file.startswith("http"):
41
- markdown_str += f"![image]({segment.file})\n\n"
42
- elif segment.file and segment.file.startswith("base64://"):
43
- logger.warning("dingtalk only support url image, not base64")
44
- continue
45
- else:
46
- logger.warning(
47
- "dingtalk only support url image, not: " + segment.file
39
+
40
+ try:
41
+ if not segment.file:
42
+ logger.warning("钉钉图片 segment 缺少 file 字段,跳过")
43
+ continue
44
+ if segment.file.startswith(("http://", "https://")):
45
+ image_url = segment.file
46
+ else:
47
+ image_url = await segment.register_to_file_service()
48
+
49
+ markdown_str = f"![image]({image_url})\n\n"
50
+
51
+ ret = await asyncio.get_event_loop().run_in_executor(
52
+ None,
53
+ client.reply_markdown,
54
+ "😄",
55
+ markdown_str,
56
+ self.message_obj.raw_message,
48
57
  )
49
- continue
58
+ logger.debug(f"send image: {ret}")
50
59
 
51
- ret = await asyncio.get_event_loop().run_in_executor(
52
- None,
53
- client.reply_markdown,
54
- "😄",
55
- markdown_str,
56
- self.message_obj.raw_message,
57
- )
58
- logger.debug(f"send image: {ret}")
60
+ except Exception as e:
61
+ logger.warning(f"钉钉图片处理失败: {e}, 跳过图片发送")
62
+ continue
59
63
 
60
64
  async def send(self, message: MessageChain):
61
65
  await self.send_with_client(self.client, message)
@@ -69,7 +73,7 @@ class DingtalkMessageEvent(AstrMessageEvent):
69
73
  else:
70
74
  buffer.chain.extend(chain.chain)
71
75
  if not buffer:
72
- return
76
+ return None
73
77
  buffer.squash_plain()
74
78
  await self.send(buffer)
75
79
  return await super().send_streaming(generator, use_fallback)