AstrBot 4.5.0__py3-none-any.whl → 4.5.2__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 +44 -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 +18 -13
  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 +47 -29
  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 +40 -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 +102 -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 +116 -0
  181. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  182. astrbot/core/star/__init__.py +16 -11
  183. astrbot/core/star/config.py +10 -15
  184. astrbot/core/star/context.py +109 -84
  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 +47 -52
  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.0.dist-info → astrbot-4.5.2.dist-info}/METADATA +4 -2
  240. astrbot-4.5.2.dist-info/RECORD +261 -0
  241. astrbot-4.5.0.dist-info/RECORD +0 -258
  242. {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,19 @@
1
- from typing import List, Dict, Type
2
- from .platform_metadata import PlatformMetadata
3
1
  from astrbot.core import logger
4
2
 
5
- platform_registry: List[PlatformMetadata] = []
3
+ from .platform_metadata import PlatformMetadata
4
+
5
+ platform_registry: list[PlatformMetadata] = []
6
6
  """维护了通过装饰器注册的平台适配器"""
7
- platform_cls_map: Dict[str, Type] = {}
7
+ platform_cls_map: dict[str, type] = {}
8
8
  """维护了平台适配器名称和适配器类的映射"""
9
9
 
10
10
 
11
11
  def register_platform_adapter(
12
12
  adapter_name: str,
13
13
  desc: str,
14
- default_config_tmpl: dict = None,
15
- adapter_display_name: str = None,
16
- logo_path: str = None,
14
+ default_config_tmpl: dict | None = None,
15
+ adapter_display_name: str | None = None,
16
+ logo_path: str | None = None,
17
17
  ):
18
18
  """用于注册平台适配器的带参装饰器。
19
19
 
@@ -24,7 +24,7 @@ def register_platform_adapter(
24
24
  def decorator(cls):
25
25
  if adapter_name in platform_cls_map:
26
26
  raise ValueError(
27
- f"平台适配器 {adapter_name} 已经注册过了,可能发生了适配器命名冲突。"
27
+ f"平台适配器 {adapter_name} 已经注册过了,可能发生了适配器命名冲突。",
28
28
  )
29
29
 
30
30
  # 添加必备选项
@@ -1,24 +1,31 @@
1
1
  import asyncio
2
2
  import re
3
- from typing import AsyncGenerator, Dict, List
3
+ from collections.abc import AsyncGenerator
4
+
4
5
  from aiocqhttp import CQHttp, Event
6
+
5
7
  from astrbot.api.event import AstrMessageEvent, MessageChain
6
8
  from astrbot.api.message_components import (
9
+ BaseMessageComponent,
10
+ File,
7
11
  Image,
8
12
  Node,
9
13
  Nodes,
10
14
  Plain,
11
15
  Record,
12
16
  Video,
13
- File,
14
- BaseMessageComponent,
15
17
  )
16
18
  from astrbot.api.platform import Group, MessageMember
17
19
 
18
20
 
19
21
  class AiocqhttpMessageEvent(AstrMessageEvent):
20
22
  def __init__(
21
- self, message_str, message_obj, platform_meta, session_id, bot: CQHttp
23
+ self,
24
+ message_str,
25
+ message_obj,
26
+ platform_meta,
27
+ session_id,
28
+ bot: CQHttp,
22
29
  ):
23
30
  super().__init__(message_str, message_obj, platform_meta, session_id)
24
31
  self.bot = bot
@@ -35,16 +42,15 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
35
42
  "file": f"base64://{bs64}",
36
43
  },
37
44
  }
38
- elif isinstance(segment, File):
45
+ if isinstance(segment, File):
39
46
  # For File segments, we need to handle the file differently
40
47
  d = await segment.to_dict()
41
48
  return d
42
- elif isinstance(segment, Video):
49
+ if isinstance(segment, Video):
43
50
  d = await segment.to_dict()
44
51
  return d
45
- else:
46
- # For other segments, we simply convert them to a dict by calling toDict
47
- return segment.toDict()
52
+ # For other segments, we simply convert them to a dict by calling toDict
53
+ return segment.toDict()
48
54
 
49
55
  @staticmethod
50
56
  async def _parse_onebot_json(message_chain: MessageChain):
@@ -78,7 +84,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
78
84
  await bot.send(event=event, message=messages)
79
85
  else:
80
86
  raise ValueError(
81
- f"无法发送消息:缺少有效的数字 session_id({session_id}) 或 event({event})"
87
+ f"无法发送消息:缺少有效的数字 session_id({session_id}) 或 event({event})",
82
88
  )
83
89
 
84
90
  @classmethod
@@ -88,7 +94,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
88
94
  message_chain: MessageChain,
89
95
  event: Event | None = None,
90
96
  is_group: bool = False,
91
- session_id: str = None,
97
+ session_id: str | None = None,
92
98
  ):
93
99
  """发送消息至 QQ 协议端(aiocqhttp)。
94
100
 
@@ -98,8 +104,8 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
98
104
  event (Event | None, optional): aiocqhttp 事件对象.
99
105
  is_group (bool, optional): 是否为群消息.
100
106
  session_id (str | None, optional): 会话 ID(群号或 QQ 号
101
- """
102
107
 
108
+ """
103
109
  # 转发消息、文件消息不能和普通消息混在一起发送
104
110
  send_one_by_one = any(
105
111
  isinstance(seg, (Node, Nodes, File)) for seg in message_chain.chain
@@ -152,7 +158,9 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
152
158
  await super().send(message)
153
159
 
154
160
  async def send_streaming(
155
- self, generator: AsyncGenerator, use_fallback: bool = False
161
+ self,
162
+ generator: AsyncGenerator,
163
+ use_fallback: bool = False,
156
164
  ):
157
165
  if not use_fallback:
158
166
  buffer = None
@@ -162,7 +170,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
162
170
  else:
163
171
  buffer.chain.extend(chain.chain)
164
172
  if not buffer:
165
- return
173
+ return None
166
174
  buffer.squash_plain()
167
175
  await self.send(buffer)
168
176
  return await super().send_streaming(generator, use_fallback)
@@ -198,7 +206,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
198
206
  group_id=group_id,
199
207
  )
200
208
 
201
- members: List[Dict] = await self.bot.call_action(
209
+ members: list[dict] = await self.bot.call_action(
202
210
  "get_group_member_list",
203
211
  group_id=group_id,
204
212
  )
@@ -1,33 +1,41 @@
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。",
27
32
  )
28
33
  class AiocqhttpAdapter(Platform):
29
34
  def __init__(
30
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
35
+ self,
36
+ platform_config: dict,
37
+ platform_settings: dict,
38
+ event_queue: asyncio.Queue,
31
39
  ) -> None:
32
40
  super().__init__(event_queue)
33
41
 
@@ -48,7 +56,7 @@ class AiocqhttpAdapter(Platform):
48
56
  import_name="aiocqhttp",
49
57
  api_timeout_sec=180,
50
58
  access_token=platform_config.get(
51
- "ws_reverse_token"
59
+ "ws_reverse_token",
52
60
  ), # 以防旧版本配置不存在
53
61
  )
54
62
 
@@ -81,7 +89,9 @@ class AiocqhttpAdapter(Platform):
81
89
  logger.info("aiocqhttp(OneBot v11) 适配器已连接。")
82
90
 
83
91
  async def send_by_session(
84
- self, session: MessageSesion, message_chain: MessageChain
92
+ self,
93
+ session: MessageSesion,
94
+ message_chain: MessageChain,
85
95
  ):
86
96
  is_group = session.message_type == MessageType.GROUP_MESSAGE
87
97
  if is_group:
@@ -97,14 +107,14 @@ class AiocqhttpAdapter(Platform):
97
107
  )
98
108
  await super().send_by_session(session, message_chain)
99
109
 
100
- async def convert_message(self, event: Event) -> AstrBotMessage:
110
+ async def convert_message(self, event: Event) -> AstrBotMessage | None:
101
111
  logger.debug(f"[aiocqhttp] RawMessage {event}")
102
112
 
103
113
  if event["post_type"] == "message":
104
114
  abm = await self._convert_handle_message_event(event)
105
115
  if abm.sender.user_id == "2854196310":
106
116
  # 屏蔽 QQ 管家的消息
107
- return
117
+ return None
108
118
  elif event["post_type"] == "notice":
109
119
  abm = await self._convert_handle_notice_event(event)
110
120
  elif event["post_type"] == "request":
@@ -118,7 +128,7 @@ class AiocqhttpAdapter(Platform):
118
128
  abm.self_id = str(event.self_id)
119
129
  abm.sender = MessageMember(user_id=str(event.user_id), nickname=event.user_id)
120
130
  abm.type = MessageType.OTHER_MESSAGE
121
- if "group_id" in event and event["group_id"]:
131
+ if event.get("group_id"):
122
132
  abm.type = MessageType.GROUP_MESSAGE
123
133
  abm.group_id = str(event.group_id)
124
134
  else:
@@ -144,7 +154,7 @@ class AiocqhttpAdapter(Platform):
144
154
  abm.self_id = str(event.self_id)
145
155
  abm.sender = MessageMember(user_id=str(event.user_id), nickname=event.user_id)
146
156
  abm.type = MessageType.OTHER_MESSAGE
147
- if "group_id" in event and event["group_id"]:
157
+ if event.get("group_id"):
148
158
  abm.group_id = str(event.group_id)
149
159
  abm.type = MessageType.GROUP_MESSAGE
150
160
  else:
@@ -167,12 +177,14 @@ class AiocqhttpAdapter(Platform):
167
177
 
168
178
  if "sub_type" in event:
169
179
  if event["sub_type"] == "poke" and "target_id" in event:
170
- abm.message.append(Poke(qq=str(event["target_id"]), type="poke")) # noqa: F405
180
+ abm.message.append(Poke(qq=str(event["target_id"]), type="poke"))
171
181
 
172
182
  return abm
173
183
 
174
184
  async def _convert_handle_message_event(
175
- self, event: Event, get_reply=True
185
+ self,
186
+ event: Event,
187
+ get_reply=True,
176
188
  ) -> AstrBotMessage:
177
189
  """OneBot V11 消息类事件
178
190
 
@@ -207,13 +219,13 @@ class AiocqhttpAdapter(Platform):
207
219
 
208
220
  message_str = ""
209
221
  if not isinstance(event.message, list):
210
- err = f"aiocqhttp: 无法识别的消息类型: {str(event.message)},此条消息将被忽略。如果您在使用 go-cqhttp,请将其配置文件中的 message.post-format 更改为 array。"
222
+ err = f"aiocqhttp: 无法识别的消息类型: {event.message!s},此条消息将被忽略。如果您在使用 go-cqhttp,请将其配置文件中的 message.post-format 更改为 array。"
211
223
  logger.critical(err)
212
224
  try:
213
- self.bot.send(event, err)
225
+ await self.bot.send(event, err)
214
226
  except BaseException as e:
215
227
  logger.error(f"回复消息失败: {e}")
216
- return
228
+ return None
217
229
 
218
230
  # 按消息段类型类型适配
219
231
  for t, m_group in itertools.groupby(event.message, key=lambda x: x["type"]):
@@ -224,7 +236,7 @@ class AiocqhttpAdapter(Platform):
224
236
  # 如果文本段为空,则跳过
225
237
  continue
226
238
  message_str += current_text
227
- a = ComponentTypes[t](text=current_text) # noqa: F405
239
+ a = ComponentTypes[t](text=current_text)
228
240
  abm.message.append(a)
229
241
 
230
242
  elif t == "file":
@@ -264,7 +276,7 @@ class AiocqhttpAdapter(Platform):
264
276
  elif t == "reply":
265
277
  for m in m_group:
266
278
  if not get_reply:
267
- a = ComponentTypes[t](**m["data"]) # noqa: F405
279
+ a = ComponentTypes[t](**m["data"])
268
280
  abm.message.append(a)
269
281
  else:
270
282
  try:
@@ -277,11 +289,12 @@ class AiocqhttpAdapter(Platform):
277
289
  new_event = Event.from_payload(reply_event_data)
278
290
  if not new_event:
279
291
  logger.error(
280
- f"无法从回复消息数据构造 Event 对象: {reply_event_data}"
292
+ f"无法从回复消息数据构造 Event 对象: {reply_event_data}",
281
293
  )
282
294
  continue
283
295
  abm_reply = await self._convert_handle_message_event(
284
- new_event, get_reply=False
296
+ new_event,
297
+ get_reply=False,
285
298
  )
286
299
 
287
300
  reply_seg = Reply(
@@ -298,10 +311,12 @@ class AiocqhttpAdapter(Platform):
298
311
  abm.message.append(reply_seg)
299
312
  except BaseException as e:
300
313
  logger.error(f"获取引用消息失败: {e}。")
301
- a = ComponentTypes[t](**m["data"]) # noqa: F405
314
+ a = ComponentTypes[t](**m["data"])
302
315
  abm.message.append(a)
303
316
  elif t == "at":
304
317
  first_at_self_processed = False
318
+ # Accumulate @ mention text for efficient concatenation
319
+ at_parts = []
305
320
 
306
321
  for m in m_group:
307
322
  try:
@@ -324,7 +339,8 @@ class AiocqhttpAdapter(Platform):
324
339
  no_cache=False,
325
340
  )
326
341
  nickname = at_info.get("nick", "") or at_info.get(
327
- "nickname", ""
342
+ "nickname",
343
+ "",
328
344
  )
329
345
  is_at_self = str(m["data"]["qq"]) in {abm.self_id, "all"}
330
346
 
@@ -332,7 +348,7 @@ class AiocqhttpAdapter(Platform):
332
348
  At(
333
349
  qq=m["data"]["qq"],
334
350
  name=nickname,
335
- )
351
+ ),
336
352
  )
337
353
 
338
354
  if is_at_self and not first_at_self_processed:
@@ -340,16 +356,18 @@ class AiocqhttpAdapter(Platform):
340
356
  first_at_self_processed = True
341
357
  else:
342
358
  # 非第一个@机器人或@其他用户,添加到message_str
343
- message_str += f" @{nickname}({m['data']['qq']}) "
359
+ at_parts.append(f" @{nickname}({m['data']['qq']}) ")
344
360
  else:
345
361
  abm.message.append(At(qq=str(m["data"]["qq"]), name=""))
346
362
  except ActionFailed as e:
347
363
  logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
348
364
  except BaseException as e:
349
365
  logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
366
+
367
+ message_str += "".join(at_parts)
350
368
  else:
351
369
  for m in m_group:
352
- a = ComponentTypes[t](**m["data"]) # noqa: F405
370
+ a = ComponentTypes[t](**m["data"])
353
371
  abm.message.append(a)
354
372
 
355
373
  abm.timestamp = int(time.time())
@@ -361,7 +379,7 @@ class AiocqhttpAdapter(Platform):
361
379
  def run(self) -> Awaitable[Any]:
362
380
  if not self.host or not self.port:
363
381
  logger.warning(
364
- "aiocqhttp: 未配置 ws_reverse_host 或 ws_reverse_port,将使用默认值:http://0.0.0.0:6199"
382
+ "aiocqhttp: 未配置 ws_reverse_host 或 ws_reverse_port,将使用默认值:http://0.0.0.0:6199",
365
383
  )
366
384
  self.host = "0.0.0.0"
367
385
  self.port = 6199
@@ -1,26 +1,28 @@
1
1
  import asyncio
2
2
  import os
3
+ import threading
3
4
  import uuid
5
+
4
6
  import aiohttp
5
7
  import dingtalk_stream
6
- import threading
8
+ from dingtalk_stream import AckMessage
7
9
 
10
+ from astrbot import logger
11
+ from astrbot.api.event import MessageChain
12
+ from astrbot.api.message_components import At, Image, Plain
8
13
  from astrbot.api.platform import (
9
- Platform,
10
14
  AstrBotMessage,
11
15
  MessageMember,
12
16
  MessageType,
17
+ Platform,
13
18
  PlatformMetadata,
14
19
  )
15
- from astrbot.api.event import MessageChain
16
- from astrbot.api.message_components import Image, Plain, At
17
20
  from astrbot.core.platform.astr_message_event import MessageSesion
18
- from .dingtalk_event import DingtalkMessageEvent
19
- from ...register import register_platform_adapter
20
- from astrbot import logger
21
- from dingtalk_stream import AckMessage
22
- from astrbot.core.utils.io import download_file
23
21
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
22
+ from astrbot.core.utils.io import download_file
23
+
24
+ from ...register import register_platform_adapter
25
+ from .dingtalk_event import DingtalkMessageEvent
24
26
 
25
27
 
26
28
  class MyEventHandler(dingtalk_stream.EventHandler):
@@ -38,7 +40,10 @@ class MyEventHandler(dingtalk_stream.EventHandler):
38
40
  @register_platform_adapter("dingtalk", "钉钉机器人官方 API 适配器")
39
41
  class DingtalkPlatformAdapter(Platform):
40
42
  def __init__(
41
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
43
+ self,
44
+ platform_config: dict,
45
+ platform_settings: dict,
46
+ event_queue: asyncio.Queue,
42
47
  ) -> None:
43
48
  super().__init__(event_queue)
44
49
 
@@ -64,12 +69,15 @@ class DingtalkPlatformAdapter(Platform):
64
69
  client = dingtalk_stream.DingTalkStreamClient(credential, logger=logger)
65
70
  client.register_all_event_handler(MyEventHandler())
66
71
  client.register_callback_handler(
67
- dingtalk_stream.ChatbotMessage.TOPIC, self.client
72
+ dingtalk_stream.ChatbotMessage.TOPIC,
73
+ self.client,
68
74
  )
69
75
  self.client_ = client # 用于 websockets 的 client
70
76
 
71
77
  async def send_by_session(
72
- self, session: MessageSesion, message_chain: MessageChain
78
+ self,
79
+ session: MessageSesion,
80
+ message_chain: MessageChain,
73
81
  ):
74
82
  raise NotImplementedError("钉钉机器人适配器不支持 send_by_session")
75
83
 
@@ -81,7 +89,8 @@ class DingtalkPlatformAdapter(Platform):
81
89
  )
82
90
 
83
91
  async def convert_msg(
84
- self, message: dingtalk_stream.ChatbotMessage
92
+ self,
93
+ message: dingtalk_stream.ChatbotMessage,
85
94
  ) -> AstrBotMessage:
86
95
  abm = AstrBotMessage()
87
96
  abm.message = []
@@ -93,15 +102,19 @@ class DingtalkPlatformAdapter(Platform):
93
102
  else MessageType.FRIEND_MESSAGE
94
103
  )
95
104
  abm.sender = MessageMember(
96
- user_id=message.sender_id, nickname=message.sender_nick
105
+ user_id=message.sender_id,
106
+ nickname=message.sender_nick,
97
107
  )
98
108
  abm.self_id = message.chatbot_user_id
99
109
  abm.message_id = message.message_id
100
110
  abm.raw_message = message
101
111
 
102
112
  if abm.type == MessageType.GROUP_MESSAGE:
103
- if message.is_in_at_list:
104
- abm.message.append(At(qq=abm.self_id))
113
+ # 处理所有被 @ 的用户(包括机器人自己,因 at_users 已包含)
114
+ if message.at_users:
115
+ for user in message.at_users:
116
+ if user.dingtalk_id:
117
+ abm.message.append(At(qq=user.dingtalk_id))
105
118
  abm.group_id = message.conversation_id
106
119
  if self.unique_session:
107
120
  abm.session_id = abm.sender.user_id
@@ -136,7 +149,10 @@ class DingtalkPlatformAdapter(Platform):
136
149
  return abm # 别忘了返回转换后的消息对象
137
150
 
138
151
  async def download_ding_file(
139
- self, download_code: str, robot_code: str, ext: str
152
+ self,
153
+ download_code: str,
154
+ robot_code: str,
155
+ ext: str,
140
156
  ) -> str:
141
157
  """下载钉钉文件
142
158
 
@@ -156,20 +172,22 @@ class DingtalkPlatformAdapter(Platform):
156
172
  }
157
173
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
158
174
  f_path = os.path.join(temp_dir, f"dingtalk_file_{uuid.uuid4()}.{ext}")
159
- async with aiohttp.ClientSession() as session:
160
- async with session.post(
175
+ async with (
176
+ aiohttp.ClientSession() as session,
177
+ session.post(
161
178
  "https://api.dingtalk.com/v1.0/robot/messageFiles/download",
162
179
  headers=headers,
163
180
  json=payload,
164
- ) as resp:
165
- if resp.status != 200:
166
- logger.error(
167
- f"下载钉钉文件失败: {resp.status}, {await resp.text()}"
168
- )
169
- return None
170
- resp_data = await resp.json()
171
- download_url = resp_data["data"]["downloadUrl"]
172
- await download_file(download_url, f_path)
181
+ ) as resp,
182
+ ):
183
+ if resp.status != 200:
184
+ logger.error(
185
+ f"下载钉钉文件失败: {resp.status}, {await resp.text()}",
186
+ )
187
+ return None
188
+ resp_data = await resp.json()
189
+ download_url = resp_data["data"]["downloadUrl"]
190
+ await download_file(download_url, f_path)
173
191
  return f_path
174
192
 
175
193
  async def get_access_token(self) -> str:
@@ -184,7 +202,7 @@ class DingtalkPlatformAdapter(Platform):
184
202
  ) as resp:
185
203
  if resp.status != 200:
186
204
  logger.error(
187
- f"获取钉钉机器人 access_token 失败: {resp.status}, {await resp.text()}"
205
+ f"获取钉钉机器人 access_token 失败: {resp.status}, {await resp.text()}",
188
206
  )
189
207
  return None
190
208
  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):
@@ -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)
@@ -1,6 +1,8 @@
1
+ import sys
2
+
1
3
  import discord
4
+
2
5
  from astrbot import logger
3
- import sys
4
6
 
5
7
  if sys.version_info >= (3, 12):
6
8
  from typing import override
@@ -12,7 +14,7 @@ else:
12
14
  class DiscordBotClient(discord.Bot):
13
15
  """Discord客户端封装"""
14
16
 
15
- def __init__(self, token: str, proxy: str = None):
17
+ def __init__(self, token: str, proxy: str | None = None):
16
18
  self.token = token
17
19
  self.proxy = proxy
18
20
 
@@ -41,7 +43,8 @@ class DiscordBotClient(discord.Bot):
41
43
  await self.on_ready_once_callback()
42
44
  except Exception as e:
43
45
  logger.error(
44
- f"[Discord] on_ready_once_callback 执行失败: {e}", exc_info=True
46
+ f"[Discord] on_ready_once_callback 执行失败: {e}",
47
+ exc_info=True,
45
48
  )
46
49
 
47
50
  def _create_message_data(self, message: discord.Message) -> dict:
@@ -84,7 +87,7 @@ class DiscordBotClient(discord.Bot):
84
87
  return
85
88
 
86
89
  logger.debug(
87
- f"[Discord] 收到原始消息 from {message.author.name}: {message.content}"
90
+ f"[Discord] 收到原始消息 from {message.author.name}: {message.content}",
88
91
  )
89
92
 
90
93
  if self.on_message_received:
@@ -103,12 +106,12 @@ class DiscordBotClient(discord.Bot):
103
106
  command_name = interaction_data.get("name", "")
104
107
  if options := interaction_data.get("options", []):
105
108
  params = " ".join(
106
- [f"{opt['name']}:{opt.get('value', '')}" for opt in options]
109
+ [f"{opt['name']}:{opt.get('value', '')}" for opt in options],
107
110
  )
108
111
  return f"/{command_name} {params}"
109
112
  return f"/{command_name}"
110
113
 
111
- elif interaction_type == discord.InteractionType.component:
114
+ if interaction_type == discord.InteractionType.component:
112
115
  custom_id = interaction_data.get("custom_id", "")
113
116
  component_type = interaction_data.get("component_type", "")
114
117
  return f"component:{custom_id}:{component_type}"