AstrBot 4.5.1__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 +4 -6
  35. astrbot/core/conversation_mgr.py +105 -36
  36. astrbot/core/core_lifecycle.py +68 -54
  37. astrbot/core/db/__init__.py +33 -18
  38. astrbot/core/db/migration/helper.py +12 -10
  39. astrbot/core/db/migration/migra_3_to_4.py +53 -34
  40. astrbot/core/db/migration/migra_45_to_46.py +1 -1
  41. astrbot/core/db/migration/shared_preferences_v3.py +2 -1
  42. astrbot/core/db/migration/sqlite_v3.py +26 -23
  43. astrbot/core/db/po.py +27 -18
  44. astrbot/core/db/sqlite.py +74 -45
  45. astrbot/core/db/vec_db/base.py +10 -14
  46. astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
  47. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
  48. astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
  49. astrbot/core/event_bus.py +8 -6
  50. astrbot/core/file_token_service.py +6 -5
  51. astrbot/core/initial_loader.py +7 -5
  52. astrbot/core/knowledge_base/chunking/__init__.py +1 -3
  53. astrbot/core/knowledge_base/chunking/base.py +1 -0
  54. astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
  55. astrbot/core/knowledge_base/chunking/recursive.py +16 -10
  56. astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
  57. astrbot/core/knowledge_base/kb_helper.py +30 -17
  58. astrbot/core/knowledge_base/kb_mgr.py +6 -7
  59. astrbot/core/knowledge_base/models.py +10 -4
  60. astrbot/core/knowledge_base/parsers/__init__.py +3 -5
  61. astrbot/core/knowledge_base/parsers/base.py +1 -0
  62. astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
  63. astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
  64. astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
  65. astrbot/core/knowledge_base/parsers/util.py +1 -1
  66. astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
  67. astrbot/core/knowledge_base/retrieval/manager.py +17 -14
  68. astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
  69. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
  70. astrbot/core/log.py +21 -13
  71. astrbot/core/message/components.py +123 -217
  72. astrbot/core/message/message_event_result.py +24 -24
  73. astrbot/core/persona_mgr.py +20 -11
  74. astrbot/core/pipeline/__init__.py +7 -7
  75. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  76. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  77. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
  78. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
  79. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  80. astrbot/core/pipeline/context.py +4 -1
  81. astrbot/core/pipeline/context_utils.py +77 -7
  82. astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
  83. astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
  84. astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
  85. astrbot/core/pipeline/process_stage/stage.py +13 -10
  86. astrbot/core/pipeline/process_stage/utils.py +6 -5
  87. astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
  88. astrbot/core/pipeline/respond/stage.py +23 -20
  89. astrbot/core/pipeline/result_decorate/stage.py +31 -23
  90. astrbot/core/pipeline/scheduler.py +12 -8
  91. astrbot/core/pipeline/session_status_check/stage.py +12 -8
  92. astrbot/core/pipeline/stage.py +10 -4
  93. astrbot/core/pipeline/waking_check/stage.py +24 -18
  94. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  95. astrbot/core/platform/__init__.py +6 -6
  96. astrbot/core/platform/astr_message_event.py +76 -110
  97. astrbot/core/platform/astrbot_message.py +11 -13
  98. astrbot/core/platform/manager.py +16 -15
  99. astrbot/core/platform/message_session.py +5 -3
  100. astrbot/core/platform/platform.py +16 -24
  101. astrbot/core/platform/platform_metadata.py +4 -4
  102. astrbot/core/platform/register.py +8 -8
  103. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
  104. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
  105. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
  106. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
  107. astrbot/core/platform/sources/discord/client.py +9 -6
  108. astrbot/core/platform/sources/discord/components.py +18 -14
  109. astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
  110. astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
  111. astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
  112. astrbot/core/platform/sources/lark/lark_event.py +21 -14
  113. astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
  114. astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
  115. astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
  116. astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
  117. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
  118. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  119. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  120. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  121. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
  122. astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
  123. astrbot/core/platform/sources/satori/satori_event.py +34 -25
  124. astrbot/core/platform/sources/slack/client.py +11 -9
  125. astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
  126. astrbot/core/platform/sources/slack/slack_event.py +34 -24
  127. astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
  128. astrbot/core/platform/sources/telegram/tg_event.py +32 -18
  129. astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
  130. astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
  131. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
  132. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
  133. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
  134. astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
  135. astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
  136. astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
  137. astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
  138. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
  139. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
  140. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
  141. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
  142. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
  143. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
  144. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
  145. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
  146. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
  147. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
  148. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
  149. astrbot/core/platform_message_history_mgr.py +5 -3
  150. astrbot/core/provider/__init__.py +2 -3
  151. astrbot/core/provider/entites.py +8 -8
  152. astrbot/core/provider/entities.py +61 -75
  153. astrbot/core/provider/func_tool_manager.py +59 -55
  154. astrbot/core/provider/manager.py +32 -22
  155. astrbot/core/provider/provider.py +72 -46
  156. astrbot/core/provider/register.py +7 -7
  157. astrbot/core/provider/sources/anthropic_source.py +48 -30
  158. astrbot/core/provider/sources/azure_tts_source.py +17 -13
  159. astrbot/core/provider/sources/coze_api_client.py +27 -17
  160. astrbot/core/provider/sources/coze_source.py +104 -87
  161. astrbot/core/provider/sources/dashscope_source.py +18 -11
  162. astrbot/core/provider/sources/dashscope_tts.py +36 -23
  163. astrbot/core/provider/sources/dify_source.py +25 -20
  164. astrbot/core/provider/sources/edge_tts_source.py +21 -17
  165. astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
  166. astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
  167. astrbot/core/provider/sources/gemini_source.py +72 -58
  168. astrbot/core/provider/sources/gemini_tts_source.py +8 -6
  169. astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
  170. astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
  171. astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
  172. astrbot/core/provider/sources/openai_embedding_source.py +6 -8
  173. astrbot/core/provider/sources/openai_source.py +77 -69
  174. astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
  175. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  176. astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
  177. astrbot/core/provider/sources/volcengine_tts.py +38 -31
  178. astrbot/core/provider/sources/whisper_api_source.py +14 -12
  179. astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
  180. astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
  181. astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
  182. astrbot/core/star/__init__.py +16 -11
  183. astrbot/core/star/config.py +10 -15
  184. astrbot/core/star/context.py +97 -75
  185. astrbot/core/star/filter/__init__.py +4 -3
  186. astrbot/core/star/filter/command.py +30 -28
  187. astrbot/core/star/filter/command_group.py +27 -24
  188. astrbot/core/star/filter/custom_filter.py +6 -5
  189. astrbot/core/star/filter/event_message_type.py +4 -2
  190. astrbot/core/star/filter/permission.py +4 -2
  191. astrbot/core/star/filter/platform_adapter_type.py +4 -2
  192. astrbot/core/star/filter/regex.py +4 -2
  193. astrbot/core/star/register/__init__.py +19 -19
  194. astrbot/core/star/register/star.py +6 -2
  195. astrbot/core/star/register/star_handler.py +96 -73
  196. astrbot/core/star/session_llm_manager.py +48 -14
  197. astrbot/core/star/session_plugin_manager.py +29 -15
  198. astrbot/core/star/star.py +1 -2
  199. astrbot/core/star/star_handler.py +13 -8
  200. astrbot/core/star/star_manager.py +151 -59
  201. astrbot/core/star/star_tools.py +44 -37
  202. astrbot/core/star/updator.py +10 -10
  203. astrbot/core/umop_config_router.py +10 -4
  204. astrbot/core/updator.py +13 -5
  205. astrbot/core/utils/astrbot_path.py +3 -5
  206. astrbot/core/utils/dify_api_client.py +33 -15
  207. astrbot/core/utils/io.py +66 -42
  208. astrbot/core/utils/log_pipe.py +1 -1
  209. astrbot/core/utils/metrics.py +7 -7
  210. astrbot/core/utils/path_util.py +15 -16
  211. astrbot/core/utils/pip_installer.py +5 -5
  212. astrbot/core/utils/session_waiter.py +19 -20
  213. astrbot/core/utils/shared_preferences.py +45 -20
  214. astrbot/core/utils/t2i/__init__.py +4 -1
  215. astrbot/core/utils/t2i/network_strategy.py +35 -26
  216. astrbot/core/utils/t2i/renderer.py +11 -5
  217. astrbot/core/utils/t2i/template_manager.py +14 -15
  218. astrbot/core/utils/tencent_record_helper.py +19 -13
  219. astrbot/core/utils/version_comparator.py +10 -13
  220. astrbot/core/zip_updator.py +43 -40
  221. astrbot/dashboard/routes/__init__.py +18 -18
  222. astrbot/dashboard/routes/auth.py +10 -8
  223. astrbot/dashboard/routes/chat.py +30 -21
  224. astrbot/dashboard/routes/config.py +92 -75
  225. astrbot/dashboard/routes/conversation.py +46 -39
  226. astrbot/dashboard/routes/file.py +4 -2
  227. astrbot/dashboard/routes/knowledge_base.py +47 -40
  228. astrbot/dashboard/routes/log.py +9 -4
  229. astrbot/dashboard/routes/persona.py +19 -16
  230. astrbot/dashboard/routes/plugin.py +69 -55
  231. astrbot/dashboard/routes/route.py +3 -1
  232. astrbot/dashboard/routes/session_management.py +130 -116
  233. astrbot/dashboard/routes/stat.py +34 -34
  234. astrbot/dashboard/routes/t2i.py +15 -12
  235. astrbot/dashboard/routes/tools.py +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.1.dist-info → astrbot-4.5.2.dist-info}/METADATA +2 -1
  240. astrbot-4.5.2.dist-info/RECORD +261 -0
  241. astrbot-4.5.1.dist-info/RECORD +0 -260
  242. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,20 @@
1
1
  from typing import TYPE_CHECKING
2
+
2
3
  from astrbot.api import logger
3
4
  from astrbot.api.event import AstrMessageEvent, MessageChain
4
- from astrbot.api.platform import AstrBotMessage, PlatformMetadata
5
5
  from astrbot.api.message_components import (
6
- Plain,
7
- Image,
8
6
  At,
9
7
  File,
10
- Record,
11
- Video,
12
- Reply,
13
8
  Forward,
9
+ Image,
14
10
  Node,
15
11
  Nodes,
12
+ Plain,
13
+ Record,
14
+ Reply,
15
+ Video,
16
16
  )
17
+ from astrbot.api.platform import AstrBotMessage, PlatformMetadata
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from .satori_adapter import SatoriPlatformAdapter
@@ -53,14 +54,17 @@ class SatoriPlatformEvent(AstrMessageEvent):
53
54
 
54
55
  @classmethod
55
56
  async def send_with_adapter(
56
- cls, adapter: "SatoriPlatformAdapter", message: MessageChain, session_id: str
57
+ cls,
58
+ adapter: "SatoriPlatformAdapter",
59
+ message: MessageChain,
60
+ session_id: str,
57
61
  ):
58
62
  try:
59
63
  content_parts = []
60
64
 
61
65
  for component in message.chain:
62
66
  component_content = await cls._convert_component_to_satori_static(
63
- component
67
+ component,
64
68
  )
65
69
  if component_content:
66
70
  content_parts.append(component_content)
@@ -92,12 +96,15 @@ class SatoriPlatformEvent(AstrMessageEvent):
92
96
  user_id = user.get("id", "") if user else ""
93
97
 
94
98
  result = await adapter.send_http_request(
95
- "POST", "/message.create", data, platform, user_id
99
+ "POST",
100
+ "/message.create",
101
+ data,
102
+ platform,
103
+ user_id,
96
104
  )
97
105
  if result:
98
106
  return result
99
- else:
100
- return None
107
+ return None
101
108
 
102
109
  except Exception as e:
103
110
  logger.error(f"Satori 消息发送异常: {e}")
@@ -140,7 +147,11 @@ class SatoriPlatformEvent(AstrMessageEvent):
140
147
  data = {"channel_id": channel_id, "content": content}
141
148
 
142
149
  result = await self.adapter.send_http_request(
143
- "POST", "/message.create", data, platform, user_id
150
+ "POST",
151
+ "/message.create",
152
+ data,
153
+ platform,
154
+ user_id,
144
155
  )
145
156
  if not result:
146
157
  logger.error("Satori 消息发送失败")
@@ -178,9 +189,9 @@ class SatoriPlatformEvent(AstrMessageEvent):
178
189
  img_chain = MessageChain(
179
190
  [
180
191
  Plain(
181
- text=f'<img src="data:image/jpeg;base64,{image_base64}"/>'
182
- )
183
- ]
192
+ text=f'<img src="data:image/jpeg;base64,{image_base64}"/>',
193
+ ),
194
+ ],
184
195
  )
185
196
  await self.send(img_chain)
186
197
  except Exception as e:
@@ -209,10 +220,10 @@ class SatoriPlatformEvent(AstrMessageEvent):
209
220
  )
210
221
  return text
211
222
 
212
- elif isinstance(component, At):
223
+ if isinstance(component, At):
213
224
  if component.qq:
214
225
  return f'<at id="{component.qq}"/>'
215
- elif component.name:
226
+ if component.name:
216
227
  return f'<at name="{component.name}"/>'
217
228
 
218
229
  elif isinstance(component, Image):
@@ -264,7 +275,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
264
275
  if node.content:
265
276
  for content_component in node.content:
266
277
  component_content = await self._convert_component_to_satori(
267
- content_component
278
+ content_component,
268
279
  )
269
280
  if component_content:
270
281
  content_parts.append(component_content)
@@ -302,10 +313,10 @@ class SatoriPlatformEvent(AstrMessageEvent):
302
313
  )
303
314
  return text
304
315
 
305
- elif isinstance(component, At):
316
+ if isinstance(component, At):
306
317
  if component.qq:
307
318
  return f'<at id="{component.qq}"/>'
308
- elif component.name:
319
+ if component.name:
309
320
  return f'<at name="{component.name}"/>'
310
321
 
311
322
  elif isinstance(component, Image):
@@ -358,7 +369,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
358
369
  if node.content:
359
370
  for content_component in node.content:
360
371
  component_content = await cls._convert_component_to_satori_static(
361
- content_component
372
+ content_component,
362
373
  )
363
374
  if component_content:
364
375
  content_parts.append(component_content)
@@ -395,8 +406,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
395
406
 
396
407
  if node_parts:
397
408
  return f"<message forward>{''.join(node_parts)}</message>"
398
- else:
399
- return ""
409
+ return ""
400
410
 
401
411
  except Exception as e:
402
412
  logger.error(f"转换合并转发消息失败: {e}")
@@ -415,8 +425,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
415
425
 
416
426
  if node_parts:
417
427
  return f"<message forward>{''.join(node_parts)}</message>"
418
- else:
419
- return ""
428
+ return ""
420
429
 
421
430
  except Exception as e:
422
431
  logger.error(f"转换合并转发消息失败: {e}")
@@ -1,14 +1,16 @@
1
- import json
2
- import hmac
3
- import hashlib
4
1
  import asyncio
2
+ import hashlib
3
+ import hmac
4
+ import json
5
5
  import logging
6
- from typing import Callable, Optional
7
- from quart import Quart, request, Response
8
- from slack_sdk.web.async_client import AsyncWebClient
6
+ from collections.abc import Callable
7
+
8
+ from quart import Quart, Response, request
9
9
  from slack_sdk.socket_mode.aiohttp import SocketModeClient
10
10
  from slack_sdk.socket_mode.request import SocketModeRequest
11
11
  from slack_sdk.socket_mode.response import SocketModeResponse
12
+ from slack_sdk.web.async_client import AsyncWebClient
13
+
12
14
  from astrbot.api import logger
13
15
 
14
16
 
@@ -22,7 +24,7 @@ class SlackWebhookClient:
22
24
  host: str = "0.0.0.0",
23
25
  port: int = 3000,
24
26
  path: str = "/slack/events",
25
- event_handler: Optional[Callable] = None,
27
+ event_handler: Callable | None = None,
26
28
  ):
27
29
  self.web_client = web_client
28
30
  self.signing_secret = signing_secret
@@ -93,7 +95,7 @@ class SlackWebhookClient:
93
95
  async def start(self):
94
96
  """启动 Webhook 服务器"""
95
97
  logger.info(
96
- f"Slack Webhook 服务器启动中,监听 {self.host}:{self.port}{self.path}..."
98
+ f"Slack Webhook 服务器启动中,监听 {self.host}:{self.port}{self.path}...",
97
99
  )
98
100
 
99
101
  await self.app.run_task(
@@ -119,7 +121,7 @@ class SlackSocketClient:
119
121
  self,
120
122
  web_client: AsyncWebClient,
121
123
  app_token: str,
122
- event_handler: Optional[Callable] = None,
124
+ event_handler: Callable | None = None,
123
125
  ):
124
126
  self.web_client = web_client
125
127
  self.app_token = app_token
@@ -1,34 +1,42 @@
1
- import time
2
1
  import asyncio
2
+ import base64
3
+ import re
4
+ import time
3
5
  import uuid
6
+ from collections.abc import Awaitable
7
+ from typing import Any
8
+
4
9
  import aiohttp
5
- import re
6
- import base64
7
- from typing import Awaitable, Any
8
- from slack_sdk.web.async_client import AsyncWebClient
9
10
  from slack_sdk.socket_mode.request import SocketModeRequest
11
+ from slack_sdk.web.async_client import AsyncWebClient
12
+
13
+ from astrbot.api import logger
14
+ from astrbot.api.event import MessageChain
15
+ from astrbot.api.message_components import *
10
16
  from astrbot.api.platform import (
11
- Platform,
12
17
  AstrBotMessage,
13
18
  MessageMember,
14
19
  MessageType,
20
+ Platform,
15
21
  PlatformMetadata,
16
22
  )
17
- from astrbot.api.event import MessageChain
18
- from .slack_event import SlackMessageEvent
19
- from .client import SlackWebhookClient, SlackSocketClient
20
- from astrbot.api.message_components import * # noqa: F403
21
- from astrbot.api import logger
22
23
  from astrbot.core.platform.astr_message_event import MessageSesion
24
+
23
25
  from ...register import register_platform_adapter
26
+ from .client import SlackSocketClient, SlackWebhookClient
27
+ from .slack_event import SlackMessageEvent
24
28
 
25
29
 
26
30
  @register_platform_adapter(
27
- "slack", "适用于 Slack 的消息平台适配器,支持 Socket Mode 和 Webhook Mode。"
31
+ "slack",
32
+ "适用于 Slack 的消息平台适配器,支持 Socket Mode 和 Webhook Mode。",
28
33
  )
29
34
  class SlackAdapter(Platform):
30
35
  def __init__(
31
- 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,
32
40
  ) -> None:
33
41
  super().__init__(event_queue)
34
42
 
@@ -43,7 +51,8 @@ class SlackAdapter(Platform):
43
51
  self.webhook_host = platform_config.get("slack_webhook_host", "0.0.0.0")
44
52
  self.webhook_port = platform_config.get("slack_webhook_port", 3000)
45
53
  self.webhook_path = platform_config.get(
46
- "slack_webhook_path", "/astrbot-slack-webhook/callback"
54
+ "slack_webhook_path",
55
+ "/astrbot-slack-webhook/callback",
47
56
  )
48
57
 
49
58
  if not self.bot_token:
@@ -69,10 +78,13 @@ class SlackAdapter(Platform):
69
78
  self.bot_self_id = None
70
79
 
71
80
  async def send_by_session(
72
- self, session: MessageSesion, message_chain: MessageChain
81
+ self,
82
+ session: MessageSesion,
83
+ message_chain: MessageChain,
73
84
  ):
74
- blocks, text = SlackMessageEvent._parse_slack_blocks(
75
- message_chain=message_chain, web_client=self.web_client
85
+ blocks, text = await SlackMessageEvent._parse_slack_blocks(
86
+ message_chain=message_chain,
87
+ web_client=self.web_client,
76
88
  )
77
89
 
78
90
  try:
@@ -150,7 +162,7 @@ class SlackAdapter(Platform):
150
162
  abm.message = []
151
163
 
152
164
  # 优先使用 blocks 字段解析消息
153
- if "blocks" in event and event["blocks"]:
165
+ if event.get("blocks"):
154
166
  abm.message = self._parse_blocks(event["blocks"])
155
167
  # 更新 message_str
156
168
  abm.message_str = ""
@@ -166,7 +178,8 @@ class SlackAdapter(Platform):
166
178
  mentioned_user = await self.web_client.users_info(user=mention)
167
179
  user_data = mentioned_user["user"]
168
180
  user_name = user_data.get("real_name") or user_data.get(
169
- "name", mention
181
+ "name",
182
+ mention,
170
183
  )
171
184
  abm.message.append(At(qq=mention, name=user_name))
172
185
  except Exception:
@@ -189,7 +202,7 @@ class SlackAdapter(Platform):
189
202
  else:
190
203
  # TODO: 下载鉴权
191
204
  abm.message.append(
192
- File(name=file_name, file=file_url, url=file_url)
205
+ File(name=file_name, file=file_url, url=file_url),
193
206
  )
194
207
 
195
208
  abm.raw_message = event
@@ -209,39 +222,41 @@ class SlackAdapter(Platform):
209
222
  if element.get("type") == "rich_text_section":
210
223
  # 处理富文本段落
211
224
  section_elements = element.get("elements", [])
212
- text_content = ""
213
-
225
+ text_parts = []
214
226
  for section_element in section_elements:
215
227
  element_type = section_element.get("type", "")
216
228
 
217
229
  if element_type == "text":
218
230
  # 普通文本
219
- text_content += section_element.get("text", "")
231
+ text_parts.append(section_element.get("text", ""))
220
232
  elif element_type == "user":
221
233
  # @用户提及
222
234
  user_id = section_element.get("user_id", "")
223
235
  if user_id:
224
236
  # 将之前的文本内容先添加到组件中
237
+ text_content = "".join(text_parts)
225
238
  if text_content.strip():
226
239
  message_components.append(
227
- Plain(text=text_content)
240
+ Plain(text=text_content),
228
241
  )
229
- text_content = ""
242
+ text_parts = []
230
243
  # 添加@提及组件
231
244
  message_components.append(At(qq=user_id, name=""))
232
245
  elif element_type == "channel":
233
246
  # #频道提及
234
247
  channel_id = section_element.get("channel_id", "")
235
- text_content += f"#{channel_id}"
248
+ text_parts.append(f"#{channel_id}")
236
249
  elif element_type == "link":
237
250
  # 链接
238
251
  url = section_element.get("url", "")
239
252
  link_text = section_element.get("text", url)
240
- text_content += f"[{link_text}]({url})"
253
+ text_parts.append(f"[{link_text}]({url})")
241
254
  elif element_type == "emoji":
242
255
  # 表情符号
243
256
  emoji_name = section_element.get("name", "")
244
- text_content += f":{emoji_name}:"
257
+ text_parts.append(f":{emoji_name}:")
258
+
259
+ text_content = "".join(text_parts)
245
260
 
246
261
  if text_content.strip():
247
262
  message_components.append(Plain(text=text_content))
@@ -307,11 +322,10 @@ class SlackAdapter(Platform):
307
322
  content = await resp.read()
308
323
  base64_content = base64.b64encode(content).decode("utf-8")
309
324
  return base64_content
310
- else:
311
- logger.error(
312
- f"Failed to download slack file: {resp.status} {await resp.text()}"
313
- )
314
- raise Exception(f"下载文件失败: {resp.status}")
325
+ logger.error(
326
+ f"Failed to download slack file: {resp.status} {await resp.text()}",
327
+ )
328
+ raise Exception(f"下载文件失败: {resp.status}")
315
329
 
316
330
  async def run(self) -> Awaitable[Any]:
317
331
  self.bot_self_id = await self.get_bot_user_id()
@@ -323,7 +337,9 @@ class SlackAdapter(Platform):
323
337
 
324
338
  # 创建 Socket 客户端
325
339
  self.socket_client = SlackSocketClient(
326
- self.web_client, self.app_token, self._handle_socket_event
340
+ self.web_client,
341
+ self.app_token,
342
+ self._handle_socket_event,
327
343
  )
328
344
 
329
345
  logger.info("Slack 适配器 (Socket Mode) 启动中...")
@@ -344,13 +360,13 @@ class SlackAdapter(Platform):
344
360
  )
345
361
 
346
362
  logger.info(
347
- f"Slack 适配器 (Webhook Mode) 启动中,监听 {self.webhook_host}:{self.webhook_port}{self.webhook_path}..."
363
+ f"Slack 适配器 (Webhook Mode) 启动中,监听 {self.webhook_host}:{self.webhook_port}{self.webhook_path}...",
348
364
  )
349
365
  await self.webhook_client.start()
350
366
 
351
367
  else:
352
368
  raise ValueError(
353
- f"不支持的连接模式: {self.connection_mode},请使用 'socket' 或 'webhook'"
369
+ f"不支持的连接模式: {self.connection_mode},请使用 'socket' 或 'webhook'",
354
370
  )
355
371
 
356
372
  async def _handle_webhook_event(self, event_data: dict):
@@ -1,16 +1,18 @@
1
1
  import asyncio
2
2
  import re
3
- from typing import AsyncGenerator
3
+ from collections.abc import AsyncGenerator
4
+
4
5
  from slack_sdk.web.async_client import AsyncWebClient
6
+
7
+ from astrbot.api import logger
5
8
  from astrbot.api.event import AstrMessageEvent, MessageChain
6
9
  from astrbot.api.message_components import (
10
+ BaseMessageComponent,
11
+ File,
7
12
  Image,
8
13
  Plain,
9
- File,
10
- BaseMessageComponent,
11
14
  )
12
15
  from astrbot.api.platform import Group, MessageMember
13
- from astrbot.api import logger
14
16
 
15
17
 
16
18
  class SlackMessageEvent(AstrMessageEvent):
@@ -27,12 +29,13 @@ class SlackMessageEvent(AstrMessageEvent):
27
29
 
28
30
  @staticmethod
29
31
  async def _from_segment_to_slack_block(
30
- segment: BaseMessageComponent, web_client: AsyncWebClient
32
+ segment: BaseMessageComponent,
33
+ web_client: AsyncWebClient,
31
34
  ) -> dict:
32
35
  """将消息段转换为 Slack 块格式"""
33
36
  if isinstance(segment, Plain):
34
37
  return {"type": "section", "text": {"type": "mrkdwn", "text": segment.text}}
35
- elif isinstance(segment, Image):
38
+ if isinstance(segment, Image):
36
39
  # upload file
37
40
  url = segment.url or segment.file
38
41
  if url.startswith("http"):
@@ -61,7 +64,7 @@ class SlackMessageEvent(AstrMessageEvent):
61
64
  },
62
65
  "alt_text": "图片",
63
66
  }
64
- elif isinstance(segment, File):
67
+ if isinstance(segment, File):
65
68
  # upload file
66
69
  url = segment.url or segment.file
67
70
  response = await web_client.files_upload_v2(
@@ -82,12 +85,12 @@ class SlackMessageEvent(AstrMessageEvent):
82
85
  "text": f"文件: <{file_url}|{segment.name or '文件'}>",
83
86
  },
84
87
  }
85
- else:
86
- return {"type": "section", "text": {"type": "mrkdwn", "text": str(segment)}}
88
+ return {"type": "section", "text": {"type": "mrkdwn", "text": str(segment)}}
87
89
 
88
90
  @staticmethod
89
91
  async def _parse_slack_blocks(
90
- message_chain: MessageChain, web_client: AsyncWebClient
92
+ message_chain: MessageChain,
93
+ web_client: AsyncWebClient,
91
94
  ):
92
95
  """解析成 Slack 块格式"""
93
96
  blocks = []
@@ -103,27 +106,29 @@ class SlackMessageEvent(AstrMessageEvent):
103
106
  {
104
107
  "type": "section",
105
108
  "text": {"type": "mrkdwn", "text": text_content},
106
- }
109
+ },
107
110
  )
108
111
  text_content = ""
109
112
 
110
113
  # 添加其他类型的块
111
114
  block = await SlackMessageEvent._from_segment_to_slack_block(
112
- segment, web_client
115
+ segment,
116
+ web_client,
113
117
  )
114
118
  blocks.append(block)
115
119
 
116
120
  # 如果最后还有文本内容
117
121
  if text_content.strip():
118
122
  blocks.append(
119
- {"type": "section", "text": {"type": "mrkdwn", "text": text_content}}
123
+ {"type": "section", "text": {"type": "mrkdwn", "text": text_content}},
120
124
  )
121
125
 
122
126
  return blocks, "" if blocks else text_content
123
127
 
124
128
  async def send(self, message: MessageChain):
125
129
  blocks, text = await SlackMessageEvent._parse_slack_blocks(
126
- message, self.web_client
130
+ message,
131
+ self.web_client,
127
132
  )
128
133
 
129
134
  try:
@@ -143,28 +148,33 @@ class SlackMessageEvent(AstrMessageEvent):
143
148
  )
144
149
  except Exception:
145
150
  # 如果块发送失败,尝试只发送文本
146
- fallback_text = ""
151
+ parts = []
147
152
  for segment in message.chain:
148
153
  if isinstance(segment, Plain):
149
- fallback_text += segment.text
154
+ parts.append(segment.text)
150
155
  elif isinstance(segment, File):
151
- fallback_text += f" [文件: {segment.name}] "
156
+ parts.append(f" [文件: {segment.name}] ")
152
157
  elif isinstance(segment, Image):
153
- fallback_text += " [图片] "
158
+ parts.append(" [图片] ")
159
+ fallback_text = "".join(parts)
154
160
 
155
161
  if self.get_group_id():
156
162
  await self.web_client.chat_postMessage(
157
- channel=self.get_group_id(), text=fallback_text
163
+ channel=self.get_group_id(),
164
+ text=fallback_text,
158
165
  )
159
166
  else:
160
167
  await self.web_client.chat_postMessage(
161
- channel=self.get_sender_id(), text=fallback_text
168
+ channel=self.get_sender_id(),
169
+ text=fallback_text,
162
170
  )
163
171
 
164
172
  await super().send(message)
165
173
 
166
174
  async def send_streaming(
167
- self, generator: AsyncGenerator, use_fallback: bool = False
175
+ self,
176
+ generator: AsyncGenerator,
177
+ use_fallback: bool = False,
168
178
  ):
169
179
  if not use_fallback:
170
180
  buffer = None
@@ -174,7 +184,7 @@ class SlackMessageEvent(AstrMessageEvent):
174
184
  else:
175
185
  buffer.chain.extend(chain.chain)
176
186
  if not buffer:
177
- return
187
+ return None
178
188
  buffer.squash_plain()
179
189
  await self.send(buffer)
180
190
  return await super().send_streaming(generator, use_fallback)
@@ -211,7 +221,7 @@ class SlackMessageEvent(AstrMessageEvent):
211
221
 
212
222
  # 获取频道成员
213
223
  members_response = await self.web_client.conversations_members(
214
- channel=channel_id
224
+ channel=channel_id,
215
225
  )
216
226
 
217
227
  members = []
@@ -224,7 +234,7 @@ class SlackMessageEvent(AstrMessageEvent):
224
234
  user_id=member_id,
225
235
  nickname=user_data.get("real_name")
226
236
  or user_data.get("name", member_id),
227
- )
237
+ ),
228
238
  )
229
239
  except Exception:
230
240
  # 如果获取用户信息失败,使用默认信息