AstrBot 4.5.1__py3-none-any.whl → 4.5.3__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 +56 -53
  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.3.dist-info}/METADATA +2 -1
  240. astrbot-4.5.3.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.3.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,4 @@
1
- """
2
- MIT License
1
+ """MIT License
3
2
 
4
3
  Copyright (c) 2021 Lxns-Network
5
4
 
@@ -26,7 +25,6 @@ import asyncio
26
25
  import base64
27
26
  import json
28
27
  import os
29
- import typing as T
30
28
  import uuid
31
29
  from enum import Enum
32
30
 
@@ -38,60 +36,36 @@ from astrbot.core.utils.io import download_file, download_image_by_url, file_to_
38
36
 
39
37
 
40
38
  class ComponentType(str, Enum):
41
- Plain = "Plain" # 纯文本消息
42
- Face = "Face" # QQ表情
43
- Record = "Record" # 语音
44
- Video = "Video" # 视频
45
- At = "At" # At
46
- Node = "Node" # 转发消息的一个节点
47
- Nodes = "Nodes" # 转发消息的多个节点
48
- Poke = "Poke" # QQ 戳一戳
49
- Image = "Image" # 图片
50
- Reply = "Reply" # 回复
51
- Forward = "Forward" # 转发消息
52
- File = "File" # 文件
53
-
39
+ # Basic Segment Types
40
+ Plain = "Plain" # plain text message
41
+ Image = "Image" # image
42
+ Record = "Record" # audio
43
+ Video = "Video" # video
44
+ File = "File" # file attachment
45
+
46
+ # IM-specific Segment Types
47
+ Face = "Face" # Emoji segment for Tencent QQ platform
48
+ At = "At" # mention a user in IM apps
49
+ Node = "Node" # a node in a forwarded message
50
+ Nodes = "Nodes" # a forwarded message consisting of multiple nodes
51
+ Poke = "Poke" # a poke message for Tencent QQ platform
52
+ Reply = "Reply" # a reply message segment
53
+ Forward = "Forward" # a forwarded message segment
54
54
  RPS = "RPS" # TODO
55
55
  Dice = "Dice" # TODO
56
56
  Shake = "Shake" # TODO
57
- Anonymous = "Anonymous" # TODO
58
57
  Share = "Share"
59
58
  Contact = "Contact" # TODO
60
59
  Location = "Location" # TODO
61
60
  Music = "Music"
62
- RedBag = "RedBag"
63
- Xml = "Xml"
64
61
  Json = "Json"
65
- CardImage = "CardImage"
66
- TTS = "TTS"
67
62
  Unknown = "Unknown"
68
-
69
63
  WechatEmoji = "WechatEmoji" # Wechat 下的 emoji 表情包
70
64
 
71
65
 
72
66
  class BaseMessageComponent(BaseModel):
73
67
  type: ComponentType
74
68
 
75
- def toString(self):
76
- output = f"[CQ:{self.type.lower()}"
77
- for k, v in self.__dict__.items():
78
- if k == "type" or v is None:
79
- continue
80
- if k == "_type":
81
- k = "type"
82
- if isinstance(v, bool):
83
- v = 1 if v else 0
84
- output += ",%s=%s" % (
85
- k,
86
- str(v)
87
- .replace("&", "&")
88
- .replace(",", ",")
89
- .replace("[", "[")
90
- .replace("]", "]"),
91
- )
92
- output += "]"
93
- return output
94
-
95
69
  def toDict(self):
96
70
  data = {}
97
71
  for k, v in self.__dict__.items():
@@ -110,18 +84,11 @@ class BaseMessageComponent(BaseModel):
110
84
  class Plain(BaseMessageComponent):
111
85
  type = ComponentType.Plain
112
86
  text: str
113
- convert: T.Optional[bool] = True # 若为 False 则直接发送未转换 CQ 码的消息
87
+ convert: bool | None = True
114
88
 
115
89
  def __init__(self, text: str, convert: bool = True, **_):
116
90
  super().__init__(text=text, convert=convert, **_)
117
91
 
118
- def toString(self): # 没有 [CQ:plain] 这种东西,所以直接导出纯文本
119
- if not self.convert:
120
- return self.text
121
- return (
122
- self.text.replace("&", "&").replace("[", "[").replace("]", "]")
123
- )
124
-
125
92
  def toDict(self):
126
93
  return {"type": "text", "data": {"text": self.text.strip()}}
127
94
 
@@ -139,17 +106,17 @@ class Face(BaseMessageComponent):
139
106
 
140
107
  class Record(BaseMessageComponent):
141
108
  type = ComponentType.Record
142
- file: T.Optional[str] = ""
143
- magic: T.Optional[bool] = False
144
- url: T.Optional[str] = ""
145
- cache: T.Optional[bool] = True
146
- proxy: T.Optional[bool] = True
147
- timeout: T.Optional[int] = 0
109
+ file: str | None = ""
110
+ magic: bool | None = False
111
+ url: str | None = ""
112
+ cache: bool | None = True
113
+ proxy: bool | None = True
114
+ timeout: int | None = 0
148
115
  # 额外
149
- path: T.Optional[str]
116
+ path: str | None
150
117
 
151
- def __init__(self, file: T.Optional[str], **_):
152
- for k in _.keys():
118
+ def __init__(self, file: str | None, **_):
119
+ for k in _:
153
120
  if k == "url":
154
121
  pass
155
122
  # Protocol.warn(f"go-cqhttp doesn't support send {self.type} by {k}")
@@ -174,15 +141,16 @@ class Record(BaseMessageComponent):
174
141
 
175
142
  Returns:
176
143
  str: 语音的本地路径,以绝对路径表示。
144
+
177
145
  """
178
146
  if not self.file:
179
147
  raise Exception(f"not a valid file: {self.file}")
180
148
  if self.file.startswith("file:///"):
181
149
  return self.file[8:]
182
- elif self.file.startswith("http"):
150
+ if self.file.startswith("http"):
183
151
  file_path = await download_image_by_url(self.file)
184
152
  return os.path.abspath(file_path)
185
- elif self.file.startswith("base64://"):
153
+ if self.file.startswith("base64://"):
186
154
  bs64_data = self.file.removeprefix("base64://")
187
155
  image_bytes = base64.b64decode(bs64_data)
188
156
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
@@ -190,16 +158,16 @@ class Record(BaseMessageComponent):
190
158
  with open(file_path, "wb") as f:
191
159
  f.write(image_bytes)
192
160
  return os.path.abspath(file_path)
193
- elif os.path.exists(self.file):
161
+ if os.path.exists(self.file):
194
162
  return os.path.abspath(self.file)
195
- else:
196
- raise Exception(f"not a valid file: {self.file}")
163
+ raise Exception(f"not a valid file: {self.file}")
197
164
 
198
165
  async def convert_to_base64(self) -> str:
199
166
  """将语音统一转换为 base64 编码。这个方法避免了手动判断语音数据类型,直接返回语音数据的 base64 编码。
200
167
 
201
168
  Returns:
202
169
  str: 语音的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
170
+
203
171
  """
204
172
  # convert to base64
205
173
  if not self.file:
@@ -219,14 +187,14 @@ class Record(BaseMessageComponent):
219
187
  return bs64_data
220
188
 
221
189
  async def register_to_file_service(self) -> str:
222
- """
223
- 将语音注册到文件服务。
190
+ """将语音注册到文件服务。
224
191
 
225
192
  Returns:
226
193
  str: 注册后的URL
227
194
 
228
195
  Raises:
229
196
  Exception: 如果未配置 callback_api_base
197
+
230
198
  """
231
199
  callback_host = astrbot_config.get("callback_api_base")
232
200
 
@@ -245,10 +213,10 @@ class Record(BaseMessageComponent):
245
213
  class Video(BaseMessageComponent):
246
214
  type = ComponentType.Video
247
215
  file: str
248
- cover: T.Optional[str] = ""
249
- c: T.Optional[int] = 2
216
+ cover: str | None = ""
217
+ c: int | None = 2
250
218
  # 额外
251
- path: T.Optional[str] = ""
219
+ path: str | None = ""
252
220
 
253
221
  def __init__(self, file: str, **_):
254
222
  super().__init__(file=file, **_)
@@ -268,32 +236,31 @@ class Video(BaseMessageComponent):
268
236
 
269
237
  Returns:
270
238
  str: 视频的本地路径,以绝对路径表示。
239
+
271
240
  """
272
241
  url = self.file
273
242
  if url and url.startswith("file:///"):
274
243
  return url[8:]
275
- elif url and url.startswith("http"):
244
+ if url and url.startswith("http"):
276
245
  download_dir = os.path.join(get_astrbot_data_path(), "temp")
277
246
  video_file_path = os.path.join(download_dir, f"{uuid.uuid4().hex}")
278
247
  await download_file(url, video_file_path)
279
248
  if os.path.exists(video_file_path):
280
249
  return os.path.abspath(video_file_path)
281
- else:
282
- raise Exception(f"download failed: {url}")
283
- elif os.path.exists(url):
250
+ raise Exception(f"download failed: {url}")
251
+ if os.path.exists(url):
284
252
  return os.path.abspath(url)
285
- else:
286
- raise Exception(f"not a valid file: {url}")
253
+ raise Exception(f"not a valid file: {url}")
287
254
 
288
255
  async def register_to_file_service(self):
289
- """
290
- 将视频注册到文件服务。
256
+ """将视频注册到文件服务。
291
257
 
292
258
  Returns:
293
259
  str: 注册后的URL
294
260
 
295
261
  Raises:
296
262
  Exception: 如果未配置 callback_api_base
263
+
297
264
  """
298
265
  callback_host = astrbot_config.get("callback_api_base")
299
266
 
@@ -330,8 +297,8 @@ class Video(BaseMessageComponent):
330
297
 
331
298
  class At(BaseMessageComponent):
332
299
  type = ComponentType.At
333
- qq: T.Union[int, str] # 此处str为all时代表所有人
334
- name: T.Optional[str] = ""
300
+ qq: int | str # 此处str为all时代表所有人
301
+ name: str | None = ""
335
302
 
336
303
  def __init__(self, **_):
337
304
  super().__init__(**_)
@@ -371,20 +338,12 @@ class Shake(BaseMessageComponent): # TODO
371
338
  super().__init__(**_)
372
339
 
373
340
 
374
- class Anonymous(BaseMessageComponent): # TODO
375
- type = ComponentType.Anonymous
376
- ignore: T.Optional[bool] = False
377
-
378
- def __init__(self, **_):
379
- super().__init__(**_)
380
-
381
-
382
341
  class Share(BaseMessageComponent):
383
342
  type = ComponentType.Share
384
343
  url: str
385
344
  title: str
386
- content: T.Optional[str] = ""
387
- image: T.Optional[str] = ""
345
+ content: str | None = ""
346
+ image: str | None = ""
388
347
 
389
348
  def __init__(self, **_):
390
349
  super().__init__(**_)
@@ -393,7 +352,7 @@ class Share(BaseMessageComponent):
393
352
  class Contact(BaseMessageComponent): # TODO
394
353
  type = ComponentType.Contact
395
354
  _type: str # type 字段冲突
396
- id: T.Optional[int] = 0
355
+ id: int | None = 0
397
356
 
398
357
  def __init__(self, **_):
399
358
  super().__init__(**_)
@@ -403,8 +362,8 @@ class Location(BaseMessageComponent): # TODO
403
362
  type = ComponentType.Location
404
363
  lat: float
405
364
  lon: float
406
- title: T.Optional[str] = ""
407
- content: T.Optional[str] = ""
365
+ title: str | None = ""
366
+ content: str | None = ""
408
367
 
409
368
  def __init__(self, **_):
410
369
  super().__init__(**_)
@@ -413,12 +372,12 @@ class Location(BaseMessageComponent): # TODO
413
372
  class Music(BaseMessageComponent):
414
373
  type = ComponentType.Music
415
374
  _type: str
416
- id: T.Optional[int] = 0
417
- url: T.Optional[str] = ""
418
- audio: T.Optional[str] = ""
419
- title: T.Optional[str] = ""
420
- content: T.Optional[str] = ""
421
- image: T.Optional[str] = ""
375
+ id: int | None = 0
376
+ url: str | None = ""
377
+ audio: str | None = ""
378
+ title: str | None = ""
379
+ content: str | None = ""
380
+ image: str | None = ""
422
381
 
423
382
  def __init__(self, **_):
424
383
  # for k in _.keys():
@@ -429,18 +388,18 @@ class Music(BaseMessageComponent):
429
388
 
430
389
  class Image(BaseMessageComponent):
431
390
  type = ComponentType.Image
432
- file: T.Optional[str] = ""
433
- _type: T.Optional[str] = ""
434
- subType: T.Optional[int] = 0
435
- url: T.Optional[str] = ""
436
- cache: T.Optional[bool] = True
437
- id: T.Optional[int] = 40000
438
- c: T.Optional[int] = 2
391
+ file: str | None = ""
392
+ _type: str | None = ""
393
+ subType: int | None = 0
394
+ url: str | None = ""
395
+ cache: bool | None = True
396
+ id: int | None = 40000
397
+ c: int | None = 2
439
398
  # 额外
440
- path: T.Optional[str] = ""
441
- file_unique: T.Optional[str] = "" # 某些平台可能有图片缓存的唯一标识
399
+ path: str | None = ""
400
+ file_unique: str | None = "" # 某些平台可能有图片缓存的唯一标识
442
401
 
443
- def __init__(self, file: T.Optional[str], **_):
402
+ def __init__(self, file: str | None, **_):
444
403
  super().__init__(file=file, **_)
445
404
 
446
405
  @staticmethod
@@ -470,16 +429,17 @@ class Image(BaseMessageComponent):
470
429
 
471
430
  Returns:
472
431
  str: 图片的本地路径,以绝对路径表示。
432
+
473
433
  """
474
434
  url = self.url or self.file
475
435
  if not url:
476
436
  raise ValueError("No valid file or URL provided")
477
437
  if url.startswith("file:///"):
478
438
  return url[8:]
479
- elif url.startswith("http"):
439
+ if url.startswith("http"):
480
440
  image_file_path = await download_image_by_url(url)
481
441
  return os.path.abspath(image_file_path)
482
- elif url.startswith("base64://"):
442
+ if url.startswith("base64://"):
483
443
  bs64_data = url.removeprefix("base64://")
484
444
  image_bytes = base64.b64decode(bs64_data)
485
445
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
@@ -487,16 +447,16 @@ class Image(BaseMessageComponent):
487
447
  with open(image_file_path, "wb") as f:
488
448
  f.write(image_bytes)
489
449
  return os.path.abspath(image_file_path)
490
- elif os.path.exists(url):
450
+ if os.path.exists(url):
491
451
  return os.path.abspath(url)
492
- else:
493
- raise Exception(f"not a valid file: {url}")
452
+ raise Exception(f"not a valid file: {url}")
494
453
 
495
454
  async def convert_to_base64(self) -> str:
496
455
  """将这个图片统一转换为 base64 编码。这个方法避免了手动判断图片数据类型,直接返回图片数据的 base64 编码。
497
456
 
498
457
  Returns:
499
458
  str: 图片的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
459
+
500
460
  """
501
461
  # convert to base64
502
462
  url = self.url or self.file
@@ -517,14 +477,14 @@ class Image(BaseMessageComponent):
517
477
  return bs64_data
518
478
 
519
479
  async def register_to_file_service(self) -> str:
520
- """
521
- 将图片注册到文件服务。
480
+ """将图片注册到文件服务。
522
481
 
523
482
  Returns:
524
483
  str: 注册后的URL
525
484
 
526
485
  Raises:
527
486
  Exception: 如果未配置 callback_api_base
487
+
528
488
  """
529
489
  callback_host = astrbot_config.get("callback_api_base")
530
490
 
@@ -542,42 +502,34 @@ class Image(BaseMessageComponent):
542
502
 
543
503
  class Reply(BaseMessageComponent):
544
504
  type = ComponentType.Reply
545
- id: T.Union[str, int]
505
+ id: str | int
546
506
  """所引用的消息 ID"""
547
- chain: T.Optional[T.List["BaseMessageComponent"]] = []
507
+ chain: list["BaseMessageComponent"] | None = []
548
508
  """被引用的消息段列表"""
549
- sender_id: T.Optional[int] | T.Optional[str] = 0
509
+ sender_id: int | None | str = 0
550
510
  """被引用的消息对应的发送者的 ID"""
551
- sender_nickname: T.Optional[str] = ""
511
+ sender_nickname: str | None = ""
552
512
  """被引用的消息对应的发送者的昵称"""
553
- time: T.Optional[int] = 0
513
+ time: int | None = 0
554
514
  """被引用的消息发送时间"""
555
- message_str: T.Optional[str] = ""
515
+ message_str: str | None = ""
556
516
  """被引用的消息解析后的纯文本消息字符串"""
557
517
 
558
- text: T.Optional[str] = ""
518
+ text: str | None = ""
559
519
  """deprecated"""
560
- qq: T.Optional[int] = 0
520
+ qq: int | None = 0
561
521
  """deprecated"""
562
- seq: T.Optional[int] = 0
522
+ seq: int | None = 0
563
523
  """deprecated"""
564
524
 
565
525
  def __init__(self, **_):
566
526
  super().__init__(**_)
567
527
 
568
528
 
569
- class RedBag(BaseMessageComponent):
570
- type = ComponentType.RedBag
571
- title: str
572
-
573
- def __init__(self, **_):
574
- super().__init__(**_)
575
-
576
-
577
529
  class Poke(BaseMessageComponent):
578
530
  type: str = ComponentType.Poke
579
- id: T.Optional[int] = 0
580
- qq: T.Optional[int] = 0
531
+ id: int | None = 0
532
+ qq: int | None = 0
581
533
 
582
534
  def __init__(self, type: str, **_):
583
535
  type = f"Poke:{type}"
@@ -596,12 +548,12 @@ class Node(BaseMessageComponent):
596
548
  """群合并转发消息"""
597
549
 
598
550
  type = ComponentType.Node
599
- id: T.Optional[int] = 0 # 忽略
600
- name: T.Optional[str] = "" # qq昵称
601
- uin: T.Optional[str] = "0" # qq号
602
- content: T.Optional[list[BaseMessageComponent]] = []
603
- seq: T.Optional[T.Union[str, list]] = "" # 忽略
604
- time: T.Optional[int] = 0 # 忽略
551
+ id: int | None = 0 # 忽略
552
+ name: str | None = "" # qq昵称
553
+ uin: str | None = "0" # qq号
554
+ content: list[BaseMessageComponent] | None = []
555
+ seq: str | list | None = "" # 忽略
556
+ time: int | None = 0 # 忽略
605
557
 
606
558
  def __init__(self, content: list[BaseMessageComponent], **_):
607
559
  if isinstance(content, Node):
@@ -619,7 +571,7 @@ class Node(BaseMessageComponent):
619
571
  {
620
572
  "type": comp.type.lower(),
621
573
  "data": {"file": f"base64://{bs64}"},
622
- }
574
+ },
623
575
  )
624
576
  elif isinstance(comp, Plain):
625
577
  # For Plain segments, we need to handle the plain differently
@@ -648,9 +600,9 @@ class Node(BaseMessageComponent):
648
600
 
649
601
  class Nodes(BaseMessageComponent):
650
602
  type = ComponentType.Nodes
651
- nodes: T.List[Node]
603
+ nodes: list[Node]
652
604
 
653
- def __init__(self, nodes: T.List[Node], **_):
605
+ def __init__(self, nodes: list[Node], **_):
654
606
  super().__init__(nodes=nodes, **_)
655
607
 
656
608
  def toDict(self):
@@ -672,19 +624,10 @@ class Nodes(BaseMessageComponent):
672
624
  return ret
673
625
 
674
626
 
675
- class Xml(BaseMessageComponent):
676
- type = ComponentType.Xml
677
- data: str
678
- resid: T.Optional[int] = 0
679
-
680
- def __init__(self, **_):
681
- super().__init__(**_)
682
-
683
-
684
627
  class Json(BaseMessageComponent):
685
628
  type = ComponentType.Json
686
- data: T.Union[str, dict]
687
- resid: T.Optional[int] = 0
629
+ data: str | dict
630
+ resid: int | None = 0
688
631
 
689
632
  def __init__(self, data, **_):
690
633
  if isinstance(data, dict):
@@ -692,50 +635,18 @@ class Json(BaseMessageComponent):
692
635
  super().__init__(data=data, **_)
693
636
 
694
637
 
695
- class CardImage(BaseMessageComponent):
696
- type = ComponentType.CardImage
697
- file: str
698
- cache: T.Optional[bool] = True
699
- minwidth: T.Optional[int] = 400
700
- minheight: T.Optional[int] = 400
701
- maxwidth: T.Optional[int] = 500
702
- maxheight: T.Optional[int] = 500
703
- source: T.Optional[str] = ""
704
- icon: T.Optional[str] = ""
705
-
706
- def __init__(self, **_):
707
- super().__init__(**_)
708
-
709
- @staticmethod
710
- def fromFileSystem(path, **_):
711
- return CardImage(file=f"file:///{os.path.abspath(path)}", **_)
712
-
713
-
714
- class TTS(BaseMessageComponent):
715
- type = ComponentType.TTS
716
- text: str
717
-
718
- def __init__(self, **_):
719
- super().__init__(**_)
720
-
721
-
722
638
  class Unknown(BaseMessageComponent):
723
639
  type = ComponentType.Unknown
724
640
  text: str
725
641
 
726
- def toString(self):
727
- return ""
728
-
729
642
 
730
643
  class File(BaseMessageComponent):
731
- """
732
- 文件消息段
733
- """
644
+ """文件消息段"""
734
645
 
735
646
  type = ComponentType.File
736
- name: T.Optional[str] = "" # 名字
737
- file_: T.Optional[str] = "" # 本地路径
738
- url: T.Optional[str] = "" # url
647
+ name: str | None = "" # 名字
648
+ file_: str | None = "" # 本地路径
649
+ url: str | None = "" # url
739
650
 
740
651
  def __init__(self, name: str, file: str = "", url: str = ""):
741
652
  """文件消息段。"""
@@ -743,11 +654,11 @@ class File(BaseMessageComponent):
743
654
 
744
655
  @property
745
656
  def file(self) -> str:
746
- """
747
- 获取文件路径,如果文件不存在但有URL,则同步下载文件
657
+ """获取文件路径,如果文件不存在但有URL,则同步下载文件
748
658
 
749
659
  Returns:
750
660
  str: 文件路径
661
+
751
662
  """
752
663
  if self.file_ and os.path.exists(self.file_):
753
664
  return os.path.abspath(self.file_)
@@ -757,19 +668,16 @@ class File(BaseMessageComponent):
757
668
  loop = asyncio.get_event_loop()
758
669
  if loop.is_running():
759
670
  logger.warning(
760
- (
761
- "不可以在异步上下文中同步等待下载! "
762
- "这个警告通常发生于某些逻辑试图通过 <File>.file 获取文件消息段的文件内容。"
763
- "请使用 await get_file() 代替直接获取 <File>.file 字段"
764
- )
671
+ "不可以在异步上下文中同步等待下载! "
672
+ "这个警告通常发生于某些逻辑试图通过 <File>.file 获取文件消息段的文件内容。"
673
+ "请使用 await get_file() 代替直接获取 <File>.file 字段",
765
674
  )
766
675
  return ""
767
- else:
768
- # 等待下载完成
769
- loop.run_until_complete(self._download_file())
676
+ # 等待下载完成
677
+ loop.run_until_complete(self._download_file())
770
678
 
771
- if self.file_ and os.path.exists(self.file_):
772
- return os.path.abspath(self.file_)
679
+ if self.file_ and os.path.exists(self.file_):
680
+ return os.path.abspath(self.file_)
773
681
  except Exception as e:
774
682
  logger.error(f"文件下载失败: {e}")
775
683
 
@@ -777,11 +685,11 @@ class File(BaseMessageComponent):
777
685
 
778
686
  @file.setter
779
687
  def file(self, value: str):
780
- """
781
- 向前兼容, 设置file属性, 传入的参数可能是文件路径或URL
688
+ """向前兼容, 设置file属性, 传入的参数可能是文件路径或URL
782
689
 
783
690
  Args:
784
691
  value (str): 文件路径或URL
692
+
785
693
  """
786
694
  if value.startswith("http://") or value.startswith("https://"):
787
695
  self.url = value
@@ -796,6 +704,7 @@ class File(BaseMessageComponent):
796
704
  注意,如果为 True,也可能返回文件路径。
797
705
  Returns:
798
706
  str: 文件路径或者 http 下载链接
707
+
799
708
  """
800
709
  if allow_return_url and self.url:
801
710
  return self.url
@@ -818,14 +727,14 @@ class File(BaseMessageComponent):
818
727
  self.file_ = os.path.abspath(file_path)
819
728
 
820
729
  async def register_to_file_service(self):
821
- """
822
- 将文件注册到文件服务。
730
+ """将文件注册到文件服务。
823
731
 
824
732
  Returns:
825
733
  str: 注册后的URL
826
734
 
827
735
  Raises:
828
736
  Exception: 如果未配置 callback_api_base
737
+
829
738
  """
830
739
  callback_host = astrbot_config.get("callback_api_base")
831
740
 
@@ -863,41 +772,38 @@ class File(BaseMessageComponent):
863
772
 
864
773
  class WechatEmoji(BaseMessageComponent):
865
774
  type = ComponentType.WechatEmoji
866
- md5: T.Optional[str] = ""
867
- md5_len: T.Optional[int] = 0
868
- cdnurl: T.Optional[str] = ""
775
+ md5: str | None = ""
776
+ md5_len: int | None = 0
777
+ cdnurl: str | None = ""
869
778
 
870
779
  def __init__(self, **_):
871
780
  super().__init__(**_)
872
781
 
873
782
 
874
783
  ComponentTypes = {
784
+ # Basic Message Segments
875
785
  "plain": Plain,
876
786
  "text": Plain,
877
- "face": Face,
787
+ "image": Image,
878
788
  "record": Record,
879
789
  "video": Video,
790
+ "file": File,
791
+ # IM-specific Message Segments
792
+ "face": Face,
880
793
  "at": At,
881
794
  "rps": RPS,
882
795
  "dice": Dice,
883
796
  "shake": Shake,
884
- "anonymous": Anonymous,
885
797
  "share": Share,
886
798
  "contact": Contact,
887
799
  "location": Location,
888
800
  "music": Music,
889
- "image": Image,
890
801
  "reply": Reply,
891
- "redbag": RedBag,
892
802
  "poke": Poke,
893
803
  "forward": Forward,
894
804
  "node": Node,
895
805
  "nodes": Nodes,
896
- "xml": Xml,
897
806
  "json": Json,
898
- "cardimage": CardImage,
899
- "tts": TTS,
900
807
  "unknown": Unknown,
901
- "file": File,
902
808
  "WechatEmoji": WechatEmoji,
903
809
  }