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,5 +1,5 @@
1
1
  import discord
2
- from typing import List
2
+
3
3
  from astrbot.api.message_components import BaseMessageComponent
4
4
 
5
5
 
@@ -11,14 +11,14 @@ class DiscordEmbed(BaseMessageComponent):
11
11
 
12
12
  def __init__(
13
13
  self,
14
- title: str = None,
15
- description: str = None,
16
- color: int = None,
17
- url: str = None,
18
- thumbnail: str = None,
19
- image: str = None,
20
- footer: str = None,
21
- fields: List[dict] = None,
14
+ title: str | None = None,
15
+ description: str | None = None,
16
+ color: int | None = None,
17
+ url: str | None = None,
18
+ thumbnail: str | None = None,
19
+ image: str | None = None,
20
+ footer: str | None = None,
21
+ fields: list[dict] | None = None,
22
22
  ):
23
23
  self.title = title
24
24
  self.description = description
@@ -66,10 +66,10 @@ class DiscordButton(BaseMessageComponent):
66
66
  def __init__(
67
67
  self,
68
68
  label: str,
69
- custom_id: str = None,
69
+ custom_id: str | None = None,
70
70
  style: str = "primary",
71
- emoji: str = None,
72
- url: str = None,
71
+ emoji: str | None = None,
72
+ url: str | None = None,
73
73
  disabled: bool = False,
74
74
  ):
75
75
  self.label = label
@@ -96,7 +96,9 @@ class DiscordView(BaseMessageComponent):
96
96
  type: str = "discord_view"
97
97
 
98
98
  def __init__(
99
- self, components: List[BaseMessageComponent] = None, timeout: float = None
99
+ self,
100
+ components: list[BaseMessageComponent] = None,
101
+ timeout: float = None,
100
102
  ):
101
103
  self.components = components or []
102
104
  self.timeout = timeout
@@ -108,7 +110,9 @@ class DiscordView(BaseMessageComponent):
108
110
  for component in self.components:
109
111
  if isinstance(component, DiscordButton):
110
112
  button_style = getattr(
111
- discord.ButtonStyle, component.style, discord.ButtonStyle.primary
113
+ discord.ButtonStyle,
114
+ component.style,
115
+ discord.ButtonStyle.primary,
112
116
  )
113
117
 
114
118
  if component.url:
@@ -1,30 +1,32 @@
1
1
  import asyncio
2
- import discord
3
- import sys
4
2
  import re
3
+ import sys
4
+ from typing import Any
5
+
6
+ import discord
5
7
  from discord.abc import Messageable
6
8
  from discord.channel import DMChannel
9
+
10
+ from astrbot import logger
11
+ from astrbot.api.event import MessageChain
12
+ from astrbot.api.message_components import File, Image, Plain
7
13
  from astrbot.api.platform import (
8
- Platform,
9
14
  AstrBotMessage,
10
15
  MessageMember,
11
- PlatformMetadata,
12
16
  MessageType,
17
+ Platform,
18
+ PlatformMetadata,
19
+ register_platform_adapter,
13
20
  )
14
- from astrbot.api.event import MessageChain
15
- from astrbot.api.message_components import Plain, Image, File
16
21
  from astrbot.core.platform.astr_message_event import MessageSesion
17
- from astrbot.api.platform import register_platform_adapter
18
- from astrbot import logger
19
- from .client import DiscordBotClient
20
- from .discord_platform_event import DiscordPlatformEvent
21
-
22
- from typing import Any, Tuple
23
22
  from astrbot.core.star.filter.command import CommandFilter
24
23
  from astrbot.core.star.filter.command_group import CommandGroupFilter
25
24
  from astrbot.core.star.star import star_map
26
25
  from astrbot.core.star.star_handler import StarHandlerMetadata, star_handlers_registry
27
26
 
27
+ from .client import DiscordBotClient
28
+ from .discord_platform_event import DiscordPlatformEvent
29
+
28
30
  if sys.version_info >= (3, 12):
29
31
  from typing import override
30
32
  else:
@@ -35,7 +37,10 @@ else:
35
37
  @register_platform_adapter("discord", "Discord 适配器 (基于 Pycord)")
36
38
  class DiscordPlatformAdapter(Platform):
37
39
  def __init__(
38
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
40
+ self,
41
+ platform_config: dict,
42
+ platform_settings: dict,
43
+ event_queue: asyncio.Queue,
39
44
  ) -> None:
40
45
  super().__init__(event_queue)
41
46
  self.config = platform_config
@@ -51,7 +56,9 @@ class DiscordPlatformAdapter(Platform):
51
56
 
52
57
  @override
53
58
  async def send_by_session(
54
- self, session: MessageSesion, message_chain: MessageChain
59
+ self,
60
+ session: MessageSesion,
61
+ message_chain: MessageChain,
55
62
  ):
56
63
  """通过会话发送消息"""
57
64
  # 创建一个 message_obj 以便在 event 中使用
@@ -71,18 +78,19 @@ class DiscordPlatformAdapter(Platform):
71
78
  message_obj.group_id = self._get_channel_id(channel)
72
79
  else:
73
80
  logger.warning(
74
- f"[Discord] Can't get channel info for {channel_id_str}, will guess message type."
81
+ f"[Discord] Can't get channel info for {channel_id_str}, will guess message type.",
75
82
  )
76
83
  message_obj.type = MessageType.GROUP_MESSAGE
77
84
  message_obj.group_id = session.session_id
78
85
 
79
86
  message_obj.message_str = message_chain.get_plain_text()
80
87
  message_obj.sender = MessageMember(
81
- user_id=str(self.client_self_id), nickname=self.client.user.display_name
88
+ user_id=str(self.client_self_id),
89
+ nickname=self.client.user.display_name,
82
90
  )
83
91
  message_obj.self_id = self.client_self_id
84
92
  message_obj.session_id = session.session_id
85
- message_obj.message = message_chain
93
+ message_obj.message = message_chain.chain
86
94
 
87
95
  # 创建临时事件对象来发送消息
88
96
  temp_event = DiscordPlatformEvent(
@@ -149,7 +157,9 @@ class DiscordPlatformAdapter(Platform):
149
157
  logger.error(f"[Discord] 适配器运行时发生意外错误: {e}", exc_info=True)
150
158
 
151
159
  def _get_message_type(
152
- self, channel: Messageable, guild_id: int | None = None
160
+ self,
161
+ channel: Messageable,
162
+ guild_id: int | None = None,
153
163
  ) -> MessageType:
154
164
  """根据 channel 对象和 guild_id 判断消息类型"""
155
165
  if guild_id is not None:
@@ -201,7 +211,8 @@ class DiscordPlatformAdapter(Platform):
201
211
  abm.group_id = self._get_channel_id(message.channel)
202
212
  abm.message_str = content
203
213
  abm.sender = MessageMember(
204
- user_id=str(message.author.id), nickname=message.author.display_name
214
+ user_id=str(message.author.id),
215
+ nickname=message.author.display_name,
205
216
  )
206
217
  message_chain = []
207
218
  if abm.message_str:
@@ -209,14 +220,14 @@ class DiscordPlatformAdapter(Platform):
209
220
  if message.attachments:
210
221
  for attachment in message.attachments:
211
222
  if attachment.content_type and attachment.content_type.startswith(
212
- "image/"
223
+ "image/",
213
224
  ):
214
225
  message_chain.append(
215
- Image(file=attachment.url, filename=attachment.filename)
226
+ Image(file=attachment.url, filename=attachment.filename),
216
227
  )
217
228
  else:
218
229
  message_chain.append(
219
- File(name=attachment.filename, url=attachment.url)
230
+ File(name=attachment.filename, url=attachment.url),
220
231
  )
221
232
  abm.message = message_chain
222
233
  abm.raw_message = message
@@ -260,7 +271,7 @@ class DiscordPlatformAdapter(Platform):
260
271
  if hasattr(message.raw_message, "guild") and message.raw_message.guild:
261
272
  try:
262
273
  bot_member = message.raw_message.guild.get_member(
263
- self.client.user.id
274
+ self.client.user.id,
264
275
  )
265
276
  except Exception:
266
277
  bot_member = None
@@ -346,7 +357,7 @@ class DiscordPlatformAdapter(Platform):
346
357
  description="指令的所有参数",
347
358
  type=discord.SlashCommandOptionType.string,
348
359
  required=False,
349
- )
360
+ ),
350
361
  ]
351
362
 
352
363
  # 创建SlashCommand
@@ -362,7 +373,7 @@ class DiscordPlatformAdapter(Platform):
362
373
 
363
374
  if registered_commands:
364
375
  logger.info(
365
- f"[Discord] 准备同步 {len(registered_commands)} 个指令: {', '.join(registered_commands)}"
376
+ f"[Discord] 准备同步 {len(registered_commands)} 个指令: {', '.join(registered_commands)}",
366
377
  )
367
378
  else:
368
379
  logger.info("[Discord] 没有发现可注册的指令。")
@@ -375,7 +386,9 @@ class DiscordPlatformAdapter(Platform):
375
386
  def _create_dynamic_callback(self, cmd_name: str):
376
387
  """为每个指令动态创建一个异步回调函数"""
377
388
 
378
- async def dynamic_callback(ctx: discord.ApplicationContext, params: str = None):
389
+ async def dynamic_callback(
390
+ ctx: discord.ApplicationContext, params: str | None = None
391
+ ):
379
392
  # 将平台特定的前缀'/'剥离,以适配通用的CommandFilter
380
393
  logger.debug(f"[Discord] 回调函数触发: {cmd_name}")
381
394
  logger.debug(f"[Discord] 回调函数参数: {ctx}")
@@ -387,7 +400,7 @@ class DiscordPlatformAdapter(Platform):
387
400
  logger.debug(
388
401
  f"[Discord] 斜杠指令 '{cmd_name}' 被触发。 "
389
402
  f"原始参数: '{params}'. "
390
- f"构建的指令字符串: '{message_str_for_filter}'"
403
+ f"构建的指令字符串: '{message_str_for_filter}'",
391
404
  )
392
405
 
393
406
  # 尝试立即响应,防止超时
@@ -404,7 +417,8 @@ class DiscordPlatformAdapter(Platform):
404
417
  abm.group_id = self._get_channel_id(ctx.channel)
405
418
  abm.message_str = message_str_for_filter
406
419
  abm.sender = MessageMember(
407
- user_id=str(ctx.author.id), nickname=ctx.author.display_name
420
+ user_id=str(ctx.author.id),
421
+ nickname=ctx.author.display_name,
408
422
  )
409
423
  abm.message = [Plain(text=message_str_for_filter)]
410
424
  abm.raw_message = ctx.interaction
@@ -419,8 +433,9 @@ class DiscordPlatformAdapter(Platform):
419
433
 
420
434
  @staticmethod
421
435
  def _extract_command_info(
422
- event_filter: Any, handler_metadata: StarHandlerMetadata
423
- ) -> Tuple[str, str, CommandFilter] | None:
436
+ event_filter: Any,
437
+ handler_metadata: StarHandlerMetadata,
438
+ ) -> tuple[str, str, CommandFilter] | None:
424
439
  """从事件过滤器中提取指令信息"""
425
440
  cmd_name = None
426
441
  # is_group = False
@@ -1,21 +1,23 @@
1
1
  import asyncio
2
- import discord
3
2
  import base64
3
+ import binascii
4
+ import sys
4
5
  from io import BytesIO
5
6
  from pathlib import Path
6
- from typing import Optional
7
- import sys
8
7
 
8
+ import discord
9
+
10
+ from astrbot import logger
9
11
  from astrbot.api.event import AstrMessageEvent, MessageChain
10
- from astrbot.api.platform import AstrBotMessage, PlatformMetadata, At
11
12
  from astrbot.api.message_components import (
12
- Plain,
13
- Image,
14
- File,
15
13
  BaseMessageComponent,
14
+ File,
15
+ Image,
16
+ Plain,
16
17
  Reply,
17
18
  )
18
- from astrbot import logger
19
+ from astrbot.api.platform import AstrBotMessage, At, PlatformMetadata
20
+
19
21
  from .client import DiscordBotClient
20
22
  from .components import DiscordEmbed, DiscordView
21
23
 
@@ -41,7 +43,7 @@ class DiscordPlatformEvent(AstrMessageEvent):
41
43
  platform_meta: PlatformMetadata,
42
44
  session_id: str,
43
45
  client: DiscordBotClient,
44
- interaction_followup_webhook: Optional[discord.Webhook] = None,
46
+ interaction_followup_webhook: discord.Webhook | None = None,
45
47
  ):
46
48
  super().__init__(message_str, message_obj, platform_meta, session_id)
47
49
  self.client = client
@@ -50,7 +52,6 @@ class DiscordPlatformEvent(AstrMessageEvent):
50
52
  @override
51
53
  async def send(self, message: MessageChain):
52
54
  """发送消息到Discord平台"""
53
-
54
55
  # 解析消息链为 Discord 所需的对象
55
56
  try:
56
57
  (
@@ -90,20 +91,19 @@ class DiscordPlatformEvent(AstrMessageEvent):
90
91
  channel = await self._get_channel()
91
92
  if not channel:
92
93
  return
93
- else:
94
- await channel.send(**kwargs)
94
+ await channel.send(**kwargs)
95
95
 
96
96
  except Exception as e:
97
97
  logger.error(f"[Discord] 发送消息时发生未知错误: {e}", exc_info=True)
98
98
 
99
99
  await super().send(message)
100
100
 
101
- async def _get_channel(self) -> Optional[discord.abc.Messageable]:
101
+ async def _get_channel(self) -> discord.abc.Messageable | None:
102
102
  """获取当前事件对应的频道对象"""
103
103
  try:
104
104
  channel_id = int(self.session_id)
105
105
  return self.client.get_channel(
106
- channel_id
106
+ channel_id,
107
107
  ) or await self.client.fetch_channel(channel_id)
108
108
  except (ValueError, discord.errors.NotFound, discord.errors.Forbidden):
109
109
  logger.error(f"[Discord] 无法获取频道 {self.session_id}")
@@ -112,20 +112,20 @@ class DiscordPlatformEvent(AstrMessageEvent):
112
112
  async def _parse_to_discord(
113
113
  self,
114
114
  message: MessageChain,
115
- ) -> tuple[str, list[discord.File], Optional[discord.ui.View], list[discord.Embed]]:
115
+ ) -> tuple[str, list[discord.File], discord.ui.View | None, list[discord.Embed]]:
116
116
  """将 MessageChain 解析为 Discord 发送所需的内容"""
117
- content = ""
117
+ content_parts = []
118
118
  files = []
119
119
  view = None
120
120
  embeds = []
121
121
  reference_message_id = None
122
122
  for i in message.chain: # 遍历消息链
123
123
  if isinstance(i, Plain): # 如果是文字类型的
124
- content += i.text
124
+ content_parts.append(i.text)
125
125
  elif isinstance(i, Reply):
126
126
  reference_message_id = i.id
127
127
  elif isinstance(i, At):
128
- content += f"<@{i.qq}>"
128
+ content_parts.append(f"<@{i.qq}>")
129
129
  elif isinstance(i, Image):
130
130
  logger.debug(f"[Discord] 开始处理 Image 组件: {i}")
131
131
  try:
@@ -146,13 +146,14 @@ class DiscordPlatformEvent(AstrMessageEvent):
146
146
  continue
147
147
 
148
148
  # 2. File URI
149
- elif file_content.startswith("file:///"):
149
+ if file_content.startswith("file:///"):
150
150
  logger.debug(f"[Discord] 处理 File URI: {file_content}")
151
151
  path = Path(file_content[8:])
152
152
  if await asyncio.to_thread(path.exists):
153
153
  file_bytes = await asyncio.to_thread(path.read_bytes)
154
154
  discord_file = discord.File(
155
- BytesIO(file_bytes), filename=filename or path.name
155
+ BytesIO(file_bytes),
156
+ filename=filename or path.name,
156
157
  )
157
158
  else:
158
159
  logger.warning(f"[Discord] 图片文件不存在: {path}")
@@ -166,7 +167,8 @@ class DiscordPlatformEvent(AstrMessageEvent):
166
167
  b64_data += "=" * (4 - missing_padding)
167
168
  img_bytes = base64.b64decode(b64_data)
168
169
  discord_file = discord.File(
169
- BytesIO(img_bytes), filename=filename or "image.png"
170
+ BytesIO(img_bytes),
171
+ filename=filename or "image.png",
170
172
  )
171
173
 
172
174
  # 4. 裸 Base64 或本地路径
@@ -179,17 +181,19 @@ class DiscordPlatformEvent(AstrMessageEvent):
179
181
  b64_data += "=" * (4 - missing_padding)
180
182
  img_bytes = base64.b64decode(b64_data)
181
183
  discord_file = discord.File(
182
- BytesIO(img_bytes), filename=filename or "image.png"
184
+ BytesIO(img_bytes),
185
+ filename=filename or "image.png",
183
186
  )
184
- except (ValueError, TypeError, base64.binascii.Error):
187
+ except (ValueError, TypeError, binascii.Error):
185
188
  logger.debug(
186
- f"[Discord] 裸 Base64 解码失败,作为本地路径处理: {file_content}"
189
+ f"[Discord] 裸 Base64 解码失败,作为本地路径处理: {file_content}",
187
190
  )
188
191
  path = Path(file_content)
189
192
  if await asyncio.to_thread(path.exists):
190
193
  file_bytes = await asyncio.to_thread(path.read_bytes)
191
194
  discord_file = discord.File(
192
- BytesIO(file_bytes), filename=filename or path.name
195
+ BytesIO(file_bytes),
196
+ filename=filename or path.name,
193
197
  )
194
198
  else:
195
199
  logger.warning(f"[Discord] 图片文件不存在: {path}")
@@ -212,11 +216,11 @@ class DiscordPlatformEvent(AstrMessageEvent):
212
216
  if await asyncio.to_thread(path.exists):
213
217
  file_bytes = await asyncio.to_thread(path.read_bytes)
214
218
  files.append(
215
- discord.File(BytesIO(file_bytes), filename=i.name)
219
+ discord.File(BytesIO(file_bytes), filename=i.name),
216
220
  )
217
221
  else:
218
222
  logger.warning(
219
- f"[Discord] 获取文件失败,路径不存在: {file_path_str}"
223
+ f"[Discord] 获取文件失败,路径不存在: {file_path_str}",
220
224
  )
221
225
  else:
222
226
  logger.warning(f"[Discord] 获取文件失败: {i.name}")
@@ -235,6 +239,7 @@ class DiscordPlatformEvent(AstrMessageEvent):
235
239
  else:
236
240
  logger.debug(f"[Discord] 忽略了不支持的消息组件: {i.type}")
237
241
 
242
+ content = "".join(content_parts)
238
243
  if len(content) > 2000:
239
244
  logger.warning("[Discord] 消息内容超过2000字符,将被截断。")
240
245
  content = content[:2000]
@@ -244,7 +249,8 @@ class DiscordPlatformEvent(AstrMessageEvent):
244
249
  """对原消息添加反应"""
245
250
  try:
246
251
  if hasattr(self.message_obj, "raw_message") and hasattr(
247
- self.message_obj.raw_message, "add_reaction"
252
+ self.message_obj.raw_message,
253
+ "add_reaction",
248
254
  ):
249
255
  await self.message_obj.raw_message.add_reaction(emoji)
250
256
  except Exception as e:
@@ -279,7 +285,8 @@ class DiscordPlatformEvent(AstrMessageEvent):
279
285
  def is_mentioned(self) -> bool:
280
286
  """判断机器人是否被@"""
281
287
  if hasattr(self.message_obj, "raw_message") and hasattr(
282
- self.message_obj.raw_message, "mentions"
288
+ self.message_obj.raw_message,
289
+ "mentions",
283
290
  ):
284
291
  return any(
285
292
  mention.id == int(self.message_obj.self_id)
@@ -290,7 +297,8 @@ class DiscordPlatformEvent(AstrMessageEvent):
290
297
  def get_mention_clean_content(self) -> str:
291
298
  """获取去除@后的清洁内容"""
292
299
  if hasattr(self.message_obj, "raw_message") and hasattr(
293
- self.message_obj.raw_message, "clean_content"
300
+ self.message_obj.raw_message,
301
+ "clean_content",
294
302
  ):
295
303
  return self.message_obj.raw_message.clean_content
296
304
  return self.message_str
@@ -1,30 +1,35 @@
1
- import base64
2
1
  import asyncio
2
+ import base64
3
3
  import json
4
4
  import re
5
5
  import uuid
6
- import astrbot.api.message_components as Comp
7
6
 
7
+ import lark_oapi as lark
8
+ from lark_oapi.api.im.v1 import *
9
+
10
+ import astrbot.api.message_components as Comp
11
+ from astrbot import logger
12
+ from astrbot.api.event import MessageChain
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
20
  from astrbot.core.platform.astr_message_event import MessageSesion
17
- from .lark_event import LarkMessageEvent
21
+
18
22
  from ...register import register_platform_adapter
19
- from astrbot import logger
20
- import lark_oapi as lark
21
- from lark_oapi.api.im.v1 import *
23
+ from .lark_event import LarkMessageEvent
22
24
 
23
25
 
24
26
  @register_platform_adapter("lark", "飞书机器人官方 API 适配器")
25
27
  class LarkPlatformAdapter(Platform):
26
28
  def __init__(
27
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
29
+ self,
30
+ platform_config: dict,
31
+ platform_settings: dict,
32
+ event_queue: asyncio.Queue,
28
33
  ) -> None:
29
34
  super().__init__(event_queue)
30
35
 
@@ -65,14 +70,16 @@ class LarkPlatformAdapter(Platform):
65
70
  )
66
71
 
67
72
  async def send_by_session(
68
- self, session: MessageSesion, message_chain: MessageChain
73
+ self,
74
+ session: MessageSesion,
75
+ message_chain: MessageChain,
69
76
  ):
70
77
  res = await LarkMessageEvent._convert_to_lark(message_chain, self.lark_api)
71
78
  wrapped = {
72
79
  "zh_cn": {
73
80
  "title": "",
74
81
  "content": res,
75
- }
82
+ },
76
83
  }
77
84
 
78
85
  if session.message_type == MessageType.GROUP_MESSAGE:
@@ -91,7 +98,7 @@ class LarkPlatformAdapter(Platform):
91
98
  .content(json.dumps(wrapped))
92
99
  .msg_type("post")
93
100
  .uuid(str(uuid.uuid4()))
94
- .build()
101
+ .build(),
95
102
  )
96
103
  .build()
97
104
  )
@@ -160,7 +167,7 @@ class LarkPlatformAdapter(Platform):
160
167
  content_json_b = _ls
161
168
  elif message.message_type == "image":
162
169
  content_json_b = [
163
- {"tag": "img", "image_key": content_json_b["image_key"], "style": []}
170
+ {"tag": "img", "image_key": content_json_b["image_key"], "style": []},
164
171
  ]
165
172
 
166
173
  if message.message_type in ("post", "image"):
@@ -200,11 +207,10 @@ class LarkPlatformAdapter(Platform):
200
207
  abm.session_id = abm.group_id
201
208
  else:
202
209
  abm.session_id = abm.sender.user_id
210
+ elif abm.type == MessageType.GROUP_MESSAGE:
211
+ abm.session_id = f"{abm.sender.user_id}%{abm.group_id}" # 也保留群组id
203
212
  else:
204
- if abm.type == MessageType.GROUP_MESSAGE:
205
- abm.session_id = f"{abm.sender.user_id}%{abm.group_id}" # 也保留群组id
206
- else:
207
- abm.session_id = abm.sender.user_id
213
+ abm.session_id = abm.sender.user_id
208
214
 
209
215
  logger.debug(abm)
210
216
  await self.handle_msg(abm)
@@ -1,27 +1,34 @@
1
+ import base64
1
2
  import json
2
3
  import os
3
4
  import uuid
4
- import base64
5
- import lark_oapi as lark
6
5
  from io import BytesIO
7
- from typing import List
8
- from astrbot.api.event import AstrMessageEvent, MessageChain
9
- from astrbot.api.message_components import Plain, Image as AstrBotImage, At
10
- from astrbot.core.utils.io import download_image_by_url
6
+
7
+ import lark_oapi as lark
11
8
  from lark_oapi.api.im.v1 import *
9
+
12
10
  from astrbot import logger
11
+ from astrbot.api.event import AstrMessageEvent, MessageChain
12
+ from astrbot.api.message_components import At, Plain
13
+ from astrbot.api.message_components import Image as AstrBotImage
13
14
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
15
+ from astrbot.core.utils.io import download_image_by_url
14
16
 
15
17
 
16
18
  class LarkMessageEvent(AstrMessageEvent):
17
19
  def __init__(
18
- self, message_str, message_obj, platform_meta, session_id, bot: lark.Client
20
+ self,
21
+ message_str,
22
+ message_obj,
23
+ platform_meta,
24
+ session_id,
25
+ bot: lark.Client,
19
26
  ):
20
27
  super().__init__(message_str, message_obj, platform_meta, session_id)
21
28
  self.bot = bot
22
29
 
23
30
  @staticmethod
24
- async def _convert_to_lark(message: MessageChain, lark_client: lark.Client) -> List:
31
+ async def _convert_to_lark(message: MessageChain, lark_client: lark.Client) -> list:
25
32
  ret = []
26
33
  _stage = []
27
34
  for comp in message.chain:
@@ -58,7 +65,7 @@ class LarkMessageEvent(AstrMessageEvent):
58
65
  CreateImageRequestBody.builder()
59
66
  .image_type("message")
60
67
  .image(image_file)
61
- .build()
68
+ .build(),
62
69
  )
63
70
  .build()
64
71
  )
@@ -83,7 +90,7 @@ class LarkMessageEvent(AstrMessageEvent):
83
90
  "zh_cn": {
84
91
  "title": "",
85
92
  "content": res,
86
- }
93
+ },
87
94
  }
88
95
 
89
96
  request = (
@@ -95,7 +102,7 @@ class LarkMessageEvent(AstrMessageEvent):
95
102
  .msg_type("post")
96
103
  .uuid(str(uuid.uuid4()))
97
104
  .reply_in_thread(False)
98
- .build()
105
+ .build(),
99
106
  )
100
107
  .build()
101
108
  )
@@ -114,14 +121,14 @@ class LarkMessageEvent(AstrMessageEvent):
114
121
  .request_body(
115
122
  CreateMessageReactionRequestBody.builder()
116
123
  .reaction_type(Emoji.builder().emoji_type(emoji).build())
117
- .build()
124
+ .build(),
118
125
  )
119
126
  .build()
120
127
  )
121
128
  response = await self.bot.im.v1.message_reaction.acreate(request)
122
129
  if not response.success():
123
130
  logger.error(f"发送飞书表情回应失败({response.code}): {response.msg}")
124
- return None
131
+ return
125
132
 
126
133
  async def send_streaming(self, generator, use_fallback: bool = False):
127
134
  buffer = None
@@ -131,7 +138,7 @@ class LarkMessageEvent(AstrMessageEvent):
131
138
  else:
132
139
  buffer.chain.extend(chain.chain)
133
140
  if not buffer:
134
- return
141
+ return None
135
142
  buffer.squash_plain()
136
143
  await self.send(buffer)
137
144
  return await super().send_streaming(generator, use_fallback)