AstrBot 3.5.6__py3-none-any.whl → 4.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. astrbot/api/__init__.py +16 -4
  2. astrbot/api/all.py +2 -1
  3. astrbot/api/event/__init__.py +5 -6
  4. astrbot/api/event/filter/__init__.py +37 -34
  5. astrbot/api/platform/__init__.py +7 -8
  6. astrbot/api/provider/__init__.py +8 -7
  7. astrbot/api/star/__init__.py +3 -4
  8. astrbot/api/util/__init__.py +2 -2
  9. astrbot/cli/__init__.py +1 -0
  10. astrbot/cli/__main__.py +18 -197
  11. astrbot/cli/commands/__init__.py +6 -0
  12. astrbot/cli/commands/cmd_conf.py +209 -0
  13. astrbot/cli/commands/cmd_init.py +56 -0
  14. astrbot/cli/commands/cmd_plug.py +245 -0
  15. astrbot/cli/commands/cmd_run.py +62 -0
  16. astrbot/cli/utils/__init__.py +18 -0
  17. astrbot/cli/utils/basic.py +76 -0
  18. astrbot/cli/utils/plugin.py +246 -0
  19. astrbot/cli/utils/version_comparator.py +90 -0
  20. astrbot/core/__init__.py +17 -19
  21. astrbot/core/agent/agent.py +14 -0
  22. astrbot/core/agent/handoff.py +38 -0
  23. astrbot/core/agent/hooks.py +30 -0
  24. astrbot/core/agent/mcp_client.py +385 -0
  25. astrbot/core/agent/message.py +175 -0
  26. astrbot/core/agent/response.py +14 -0
  27. astrbot/core/agent/run_context.py +22 -0
  28. astrbot/core/agent/runners/__init__.py +3 -0
  29. astrbot/core/agent/runners/base.py +65 -0
  30. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  31. astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
  32. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  33. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  34. astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
  35. astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
  36. astrbot/core/agent/tool.py +285 -0
  37. astrbot/core/agent/tool_executor.py +17 -0
  38. astrbot/core/astr_agent_context.py +19 -0
  39. astrbot/core/astr_agent_hooks.py +36 -0
  40. astrbot/core/astr_agent_run_util.py +80 -0
  41. astrbot/core/astr_agent_tool_exec.py +246 -0
  42. astrbot/core/astrbot_config_mgr.py +275 -0
  43. astrbot/core/config/__init__.py +2 -2
  44. astrbot/core/config/astrbot_config.py +60 -20
  45. astrbot/core/config/default.py +1972 -453
  46. astrbot/core/config/i18n_utils.py +110 -0
  47. astrbot/core/conversation_mgr.py +285 -75
  48. astrbot/core/core_lifecycle.py +167 -62
  49. astrbot/core/db/__init__.py +305 -102
  50. astrbot/core/db/migration/helper.py +69 -0
  51. astrbot/core/db/migration/migra_3_to_4.py +357 -0
  52. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  53. astrbot/core/db/migration/migra_webchat_session.py +131 -0
  54. astrbot/core/db/migration/shared_preferences_v3.py +48 -0
  55. astrbot/core/db/migration/sqlite_v3.py +497 -0
  56. astrbot/core/db/po.py +259 -55
  57. astrbot/core/db/sqlite.py +773 -528
  58. astrbot/core/db/vec_db/base.py +73 -0
  59. astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
  60. astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
  61. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
  62. astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
  63. astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
  64. astrbot/core/event_bus.py +26 -22
  65. astrbot/core/exceptions.py +9 -0
  66. astrbot/core/file_token_service.py +98 -0
  67. astrbot/core/initial_loader.py +19 -10
  68. astrbot/core/knowledge_base/chunking/__init__.py +9 -0
  69. astrbot/core/knowledge_base/chunking/base.py +25 -0
  70. astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
  71. astrbot/core/knowledge_base/chunking/recursive.py +161 -0
  72. astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
  73. astrbot/core/knowledge_base/kb_helper.py +642 -0
  74. astrbot/core/knowledge_base/kb_mgr.py +330 -0
  75. astrbot/core/knowledge_base/models.py +120 -0
  76. astrbot/core/knowledge_base/parsers/__init__.py +13 -0
  77. astrbot/core/knowledge_base/parsers/base.py +51 -0
  78. astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
  79. astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
  80. astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
  81. astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
  82. astrbot/core/knowledge_base/parsers/util.py +13 -0
  83. astrbot/core/knowledge_base/prompts.py +65 -0
  84. astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
  85. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  86. astrbot/core/knowledge_base/retrieval/manager.py +276 -0
  87. astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
  88. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
  89. astrbot/core/log.py +21 -15
  90. astrbot/core/message/components.py +413 -287
  91. astrbot/core/message/message_event_result.py +35 -24
  92. astrbot/core/persona_mgr.py +192 -0
  93. astrbot/core/pipeline/__init__.py +14 -14
  94. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  95. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  96. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
  97. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
  98. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  99. astrbot/core/pipeline/context.py +7 -1
  100. astrbot/core/pipeline/context_utils.py +107 -0
  101. astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
  102. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  103. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
  104. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  105. astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
  106. astrbot/core/pipeline/process_stage/stage.py +21 -15
  107. astrbot/core/pipeline/process_stage/utils.py +125 -0
  108. astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
  109. astrbot/core/pipeline/respond/stage.py +142 -101
  110. astrbot/core/pipeline/result_decorate/stage.py +124 -57
  111. astrbot/core/pipeline/scheduler.py +21 -16
  112. astrbot/core/pipeline/session_status_check/stage.py +37 -0
  113. astrbot/core/pipeline/stage.py +11 -76
  114. astrbot/core/pipeline/waking_check/stage.py +69 -33
  115. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  116. astrbot/core/platform/__init__.py +6 -6
  117. astrbot/core/platform/astr_message_event.py +107 -129
  118. astrbot/core/platform/astrbot_message.py +32 -12
  119. astrbot/core/platform/manager.py +62 -18
  120. astrbot/core/platform/message_session.py +30 -0
  121. astrbot/core/platform/platform.py +16 -24
  122. astrbot/core/platform/platform_metadata.py +9 -4
  123. astrbot/core/platform/register.py +12 -7
  124. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
  125. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
  126. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
  127. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
  128. astrbot/core/platform/sources/discord/client.py +129 -0
  129. astrbot/core/platform/sources/discord/components.py +139 -0
  130. astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
  131. astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
  132. astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
  133. astrbot/core/platform/sources/lark/lark_event.py +39 -13
  134. astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
  135. astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
  136. astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
  137. astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
  138. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
  139. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  140. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  141. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  142. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
  143. astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
  144. astrbot/core/platform/sources/satori/satori_event.py +432 -0
  145. astrbot/core/platform/sources/slack/client.py +164 -0
  146. astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
  147. astrbot/core/platform/sources/slack/slack_event.py +253 -0
  148. astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
  149. astrbot/core/platform/sources/telegram/tg_event.py +136 -36
  150. astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
  151. astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
  152. astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
  153. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
  154. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
  155. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
  156. astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
  157. astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
  158. astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
  159. astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
  160. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
  161. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
  162. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
  163. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
  164. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
  165. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
  166. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
  167. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
  168. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
  169. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
  170. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
  171. astrbot/core/platform_message_history_mgr.py +49 -0
  172. astrbot/core/provider/__init__.py +2 -3
  173. astrbot/core/provider/entites.py +8 -8
  174. astrbot/core/provider/entities.py +154 -98
  175. astrbot/core/provider/func_tool_manager.py +446 -458
  176. astrbot/core/provider/manager.py +345 -207
  177. astrbot/core/provider/provider.py +188 -73
  178. astrbot/core/provider/register.py +9 -7
  179. astrbot/core/provider/sources/anthropic_source.py +295 -115
  180. astrbot/core/provider/sources/azure_tts_source.py +224 -0
  181. astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
  182. astrbot/core/provider/sources/dashscope_tts.py +138 -14
  183. astrbot/core/provider/sources/edge_tts_source.py +24 -19
  184. astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
  185. astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
  186. astrbot/core/provider/sources/gemini_source.py +310 -132
  187. astrbot/core/provider/sources/gemini_tts_source.py +81 -0
  188. astrbot/core/provider/sources/groq_source.py +15 -0
  189. astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
  190. astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
  191. astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
  192. astrbot/core/provider/sources/openai_embedding_source.py +40 -0
  193. astrbot/core/provider/sources/openai_source.py +241 -145
  194. astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
  195. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  196. astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
  197. astrbot/core/provider/sources/volcengine_tts.py +115 -0
  198. astrbot/core/provider/sources/whisper_api_source.py +18 -13
  199. astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
  200. astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
  201. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  202. astrbot/core/provider/sources/zhipu_source.py +6 -73
  203. astrbot/core/star/__init__.py +43 -11
  204. astrbot/core/star/config.py +17 -18
  205. astrbot/core/star/context.py +362 -138
  206. astrbot/core/star/filter/__init__.py +4 -3
  207. astrbot/core/star/filter/command.py +111 -35
  208. astrbot/core/star/filter/command_group.py +46 -34
  209. astrbot/core/star/filter/custom_filter.py +6 -5
  210. astrbot/core/star/filter/event_message_type.py +4 -2
  211. astrbot/core/star/filter/permission.py +4 -2
  212. astrbot/core/star/filter/platform_adapter_type.py +45 -12
  213. astrbot/core/star/filter/regex.py +4 -2
  214. astrbot/core/star/register/__init__.py +19 -15
  215. astrbot/core/star/register/star.py +41 -13
  216. astrbot/core/star/register/star_handler.py +236 -86
  217. astrbot/core/star/session_llm_manager.py +280 -0
  218. astrbot/core/star/session_plugin_manager.py +170 -0
  219. astrbot/core/star/star.py +36 -43
  220. astrbot/core/star/star_handler.py +47 -85
  221. astrbot/core/star/star_manager.py +442 -260
  222. astrbot/core/star/star_tools.py +167 -45
  223. astrbot/core/star/updator.py +17 -20
  224. astrbot/core/umop_config_router.py +106 -0
  225. astrbot/core/updator.py +38 -13
  226. astrbot/core/utils/astrbot_path.py +39 -0
  227. astrbot/core/utils/command_parser.py +1 -1
  228. astrbot/core/utils/io.py +119 -60
  229. astrbot/core/utils/log_pipe.py +1 -1
  230. astrbot/core/utils/metrics.py +11 -10
  231. astrbot/core/utils/migra_helper.py +73 -0
  232. astrbot/core/utils/path_util.py +63 -62
  233. astrbot/core/utils/pip_installer.py +37 -15
  234. astrbot/core/utils/session_lock.py +29 -0
  235. astrbot/core/utils/session_waiter.py +19 -20
  236. astrbot/core/utils/shared_preferences.py +174 -34
  237. astrbot/core/utils/t2i/__init__.py +4 -1
  238. astrbot/core/utils/t2i/local_strategy.py +386 -238
  239. astrbot/core/utils/t2i/network_strategy.py +109 -49
  240. astrbot/core/utils/t2i/renderer.py +29 -14
  241. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  242. astrbot/core/utils/t2i/template_manager.py +111 -0
  243. astrbot/core/utils/tencent_record_helper.py +115 -1
  244. astrbot/core/utils/version_comparator.py +10 -13
  245. astrbot/core/zip_updator.py +112 -65
  246. astrbot/dashboard/routes/__init__.py +20 -13
  247. astrbot/dashboard/routes/auth.py +20 -9
  248. astrbot/dashboard/routes/chat.py +297 -141
  249. astrbot/dashboard/routes/config.py +652 -55
  250. astrbot/dashboard/routes/conversation.py +107 -37
  251. astrbot/dashboard/routes/file.py +26 -0
  252. astrbot/dashboard/routes/knowledge_base.py +1244 -0
  253. astrbot/dashboard/routes/log.py +27 -2
  254. astrbot/dashboard/routes/persona.py +202 -0
  255. astrbot/dashboard/routes/plugin.py +197 -139
  256. astrbot/dashboard/routes/route.py +27 -7
  257. astrbot/dashboard/routes/session_management.py +354 -0
  258. astrbot/dashboard/routes/stat.py +85 -18
  259. astrbot/dashboard/routes/static_file.py +5 -2
  260. astrbot/dashboard/routes/t2i.py +233 -0
  261. astrbot/dashboard/routes/tools.py +184 -120
  262. astrbot/dashboard/routes/update.py +59 -36
  263. astrbot/dashboard/server.py +96 -36
  264. astrbot/dashboard/utils.py +165 -0
  265. astrbot-4.7.0.dist-info/METADATA +294 -0
  266. astrbot-4.7.0.dist-info/RECORD +274 -0
  267. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
  268. astrbot/core/db/plugin/sqlite_impl.py +0 -112
  269. astrbot/core/db/sqlite_init.sql +0 -50
  270. astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
  271. astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
  272. astrbot/core/platform/sources/gewechat/client.py +0 -806
  273. astrbot/core/platform/sources/gewechat/downloader.py +0 -55
  274. astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
  275. astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
  276. astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
  277. astrbot/core/provider/sources/dashscope_source.py +0 -203
  278. astrbot/core/provider/sources/dify_source.py +0 -281
  279. astrbot/core/provider/sources/llmtuner_source.py +0 -132
  280. astrbot/core/rag/embedding/openai_source.py +0 -20
  281. astrbot/core/rag/knowledge_db_mgr.py +0 -94
  282. astrbot/core/rag/store/__init__.py +0 -9
  283. astrbot/core/rag/store/chroma_db.py +0 -42
  284. astrbot/core/utils/dify_api_client.py +0 -152
  285. astrbot-3.5.6.dist-info/METADATA +0 -249
  286. astrbot-3.5.6.dist-info/RECORD +0 -158
  287. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
  288. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,23 +1,28 @@
1
- import traceback
2
1
  import asyncio
2
+ import traceback
3
+ from asyncio import Queue
4
+
5
+ from astrbot.core import logger
3
6
  from astrbot.core.config.astrbot_config import AstrBotConfig
7
+ from astrbot.core.star.star_handler import EventType, star_handlers_registry, star_map
8
+
4
9
  from .platform import Platform
5
- from typing import List
6
- from asyncio import Queue
7
10
  from .register import platform_cls_map
8
- from astrbot.core import logger
9
11
  from .sources.webchat.webchat_adapter import WebChatAdapter
10
12
 
11
13
 
12
14
  class PlatformManager:
13
15
  def __init__(self, config: AstrBotConfig, event_queue: Queue):
14
- self.platform_insts: List[Platform] = []
16
+ self.platform_insts: list[Platform] = []
15
17
  """加载的 Platform 的实例"""
16
18
 
17
19
  self._inst_map = {}
18
20
 
19
21
  self.platforms_config = config["platform"]
20
22
  self.settings = config["platform_settings"]
23
+ """NOTE: 这里是 default 的配置文件,以保证最大的兼容性;
24
+ 这个配置中的 unique_session 需要特殊处理,
25
+ 约定整个项目中对 unique_session 的引用都从 default 的配置中获取"""
21
26
  self.event_queue = event_queue
22
27
 
23
28
  async def initialize(self):
@@ -32,7 +37,7 @@ class PlatformManager:
32
37
  webchat_inst = WebChatAdapter({}, self.settings, self.event_queue)
33
38
  self.platform_insts.append(webchat_inst)
34
39
  asyncio.create_task(
35
- self._task_wrapper(asyncio.create_task(webchat_inst.run(), name="webchat"))
40
+ self._task_wrapper(asyncio.create_task(webchat_inst.run(), name="webchat")),
36
41
  )
37
42
 
38
43
  async def load_platform(self, platform_config: dict):
@@ -43,7 +48,7 @@ class PlatformManager:
43
48
  return
44
49
 
45
50
  logger.info(
46
- f"载入 {platform_config['type']}({platform_config['id']}) 平台适配器 ..."
51
+ f"载入 {platform_config['type']}({platform_config['id']}) 平台适配器 ...",
47
52
  )
48
53
  match platform_config["type"]:
49
54
  case "aiocqhttp":
@@ -58,30 +63,58 @@ class PlatformManager:
58
63
  from .sources.qqofficial_webhook.qo_webhook_adapter import (
59
64
  QQOfficialWebhookPlatformAdapter, # noqa: F401
60
65
  )
61
- case "gewechat":
62
- from .sources.gewechat.gewechat_platform_adapter import (
63
- GewechatPlatformAdapter, # noqa: F401
66
+ case "wechatpadpro":
67
+ from .sources.wechatpadpro.wechatpadpro_adapter import (
68
+ WeChatPadProAdapter, # noqa: F401
64
69
  )
65
70
  case "lark":
66
- from .sources.lark.lark_adapter import LarkPlatformAdapter # noqa: F401
71
+ from .sources.lark.lark_adapter import (
72
+ LarkPlatformAdapter, # noqa: F401
73
+ )
67
74
  case "dingtalk":
68
75
  from .sources.dingtalk.dingtalk_adapter import (
69
76
  DingtalkPlatformAdapter, # noqa: F401
70
77
  )
71
78
  case "telegram":
72
- from .sources.telegram.tg_adapter import TelegramPlatformAdapter # noqa: F401
79
+ from .sources.telegram.tg_adapter import (
80
+ TelegramPlatformAdapter, # noqa: F401
81
+ )
73
82
  case "wecom":
74
- from .sources.wecom.wecom_adapter import WecomPlatformAdapter # noqa: F401
83
+ from .sources.wecom.wecom_adapter import (
84
+ WecomPlatformAdapter, # noqa: F401
85
+ )
86
+ case "wecom_ai_bot":
87
+ from .sources.wecom_ai_bot.wecomai_adapter import (
88
+ WecomAIBotAdapter, # noqa: F401
89
+ )
90
+ case "weixin_official_account":
91
+ from .sources.weixin_official_account.weixin_offacc_adapter import (
92
+ WeixinOfficialAccountPlatformAdapter, # noqa: F401
93
+ )
94
+ case "discord":
95
+ from .sources.discord.discord_platform_adapter import (
96
+ DiscordPlatformAdapter, # noqa: F401
97
+ )
98
+ case "misskey":
99
+ from .sources.misskey.misskey_adapter import (
100
+ MisskeyPlatformAdapter, # noqa: F401
101
+ )
102
+ case "slack":
103
+ from .sources.slack.slack_adapter import SlackAdapter # noqa: F401
104
+ case "satori":
105
+ from .sources.satori.satori_adapter import (
106
+ SatoriPlatformAdapter, # noqa: F401
107
+ )
75
108
  except (ImportError, ModuleNotFoundError) as e:
76
109
  logger.error(
77
- f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。请检查依赖库是否安装。提示:可以在 管理面板->控制台->安装Pip库 中安装依赖库。"
110
+ f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。请检查依赖库是否安装。提示:可以在 管理面板->控制台->安装Pip库 中安装依赖库。",
78
111
  )
79
112
  except Exception as e:
80
113
  logger.error(f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。")
81
114
 
82
115
  if platform_config["type"] not in platform_cls_map:
83
116
  logger.error(
84
- f"未找到适用于 {platform_config['type']}({platform_config['id']}) 平台适配器,请检查是否已经安装或者名称填写错误"
117
+ f"未找到适用于 {platform_config['type']}({platform_config['id']}) 平台适配器,请检查是否已经安装或者名称填写错误",
85
118
  )
86
119
  return
87
120
  cls_type = platform_cls_map[platform_config["type"]]
@@ -97,9 +130,20 @@ class PlatformManager:
97
130
  asyncio.create_task(
98
131
  inst.run(),
99
132
  name=f"platform_{platform_config['type']}_{platform_config['id']}",
100
- )
101
- )
133
+ ),
134
+ ),
102
135
  )
136
+ handlers = star_handlers_registry.get_handlers_by_event_type(
137
+ EventType.OnPlatformLoadedEvent,
138
+ )
139
+ for handler in handlers:
140
+ try:
141
+ logger.info(
142
+ f"hook(on_platform_loaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
143
+ )
144
+ await handler.handler()
145
+ except Exception:
146
+ logger.error(traceback.format_exc())
103
147
 
104
148
  async def _task_wrapper(self, task: asyncio.Task):
105
149
  try:
@@ -137,7 +181,7 @@ class PlatformManager:
137
181
  inst
138
182
  for inst in self.platform_insts
139
183
  if inst.client_self_id == client_id
140
- )
184
+ ),
141
185
  )
142
186
  except Exception:
143
187
  logger.warning(f"可能未完全移除 {platform_id} 平台适配器")
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+
3
+ from astrbot.core.platform.message_type import MessageType
4
+
5
+
6
+ @dataclass
7
+ class MessageSession:
8
+ """描述一条消息在 AstrBot 中对应的会话的唯一标识。
9
+ 如果您需要实例化 MessageSession,请不要给 platform_id 赋值(或者同时给 platform_name 和 platform_id 赋值相同值)。它会在 __post_init__ 中自动设置为 platform_name 的值。
10
+ """
11
+
12
+ platform_name: str
13
+ """平台适配器实例的唯一标识符。自 AstrBot v4.0.0 起,该字段实际为 platform_id。"""
14
+ message_type: MessageType
15
+ session_id: str
16
+ platform_id: str | None = None
17
+
18
+ def __str__(self):
19
+ return f"{self.platform_id}:{self.message_type.value}:{self.session_id}"
20
+
21
+ def __post_init__(self):
22
+ self.platform_id = self.platform_name
23
+
24
+ @staticmethod
25
+ def from_str(session_str: str):
26
+ platform_id, message_type, session_id = session_str.split(":")
27
+ return MessageSession(platform_id, MessageType(message_type), session_id)
28
+
29
+
30
+ MessageSesion = MessageSession # back compatibility
@@ -1,13 +1,16 @@
1
1
  import abc
2
2
  import uuid
3
- from typing import Awaitable, Any
4
3
  from asyncio import Queue
5
- from .platform_metadata import PlatformMetadata
6
- from .astr_message_event import AstrMessageEvent
4
+ from collections.abc import Awaitable
5
+ from typing import Any
6
+
7
7
  from astrbot.core.message.message_event_result import MessageChain
8
- from .astr_message_event import MessageSesion
9
8
  from astrbot.core.utils.metrics import Metric
10
9
 
10
+ from .astr_message_event import AstrMessageEvent
11
+ from .message_session import MessageSesion
12
+ from .platform_metadata import PlatformMetadata
13
+
11
14
 
12
15
  class Platform(abc.ABC):
13
16
  def __init__(self, event_queue: Queue):
@@ -18,42 +21,31 @@ class Platform(abc.ABC):
18
21
 
19
22
  @abc.abstractmethod
20
23
  def run(self) -> Awaitable[Any]:
21
- """
22
- 得到一个平台的运行实例,需要返回一个协程对象。
23
- """
24
+ """得到一个平台的运行实例,需要返回一个协程对象。"""
24
25
  raise NotImplementedError
25
26
 
26
27
  async def terminate(self):
27
- """
28
- 终止一个平台的运行实例。
29
- """
30
- ...
28
+ """终止一个平台的运行实例。"""
31
29
 
32
30
  @abc.abstractmethod
33
31
  def meta(self) -> PlatformMetadata:
34
- """
35
- 得到一个平台的元数据。
36
- """
32
+ """得到一个平台的元数据。"""
37
33
  raise NotImplementedError
38
34
 
39
35
  async def send_by_session(
40
- self, session: MessageSesion, message_chain: MessageChain
36
+ self,
37
+ session: MessageSesion,
38
+ message_chain: MessageChain,
41
39
  ) -> Awaitable[Any]:
42
- """
43
- 通过会话发送消息。该方法旨在让插件能够直接通过**可持久化的会话数据**发送消息,而不需要保存 event 对象。
40
+ """通过会话发送消息。该方法旨在让插件能够直接通过**可持久化的会话数据**发送消息,而不需要保存 event 对象。
44
41
 
45
42
  异步方法。
46
43
  """
47
44
  await Metric.upload(msg_event_tick=1, adapter_name=self.meta().name)
48
45
 
49
46
  def commit_event(self, event: AstrMessageEvent):
50
- """
51
- 提交一个事件到事件队列。
52
- """
47
+ """提交一个事件到事件队列。"""
53
48
  self._event_queue.put_nowait(event)
54
49
 
55
50
  def get_client(self):
56
- """
57
- 获取平台的客户端对象。
58
- """
59
- pass
51
+ """获取平台的客户端对象。"""
@@ -4,13 +4,18 @@ from dataclasses import dataclass
4
4
  @dataclass
5
5
  class PlatformMetadata:
6
6
  name: str
7
- """平台的名称"""
7
+ """平台的名称,即平台的类型,如 aiocqhttp, discord, slack"""
8
8
  description: str
9
9
  """平台的描述"""
10
- id: str = None
10
+ id: str | None = None
11
11
  """平台的唯一标识符,用于配置中识别特定平台"""
12
12
 
13
- default_config_tmpl: dict = None
13
+ default_config_tmpl: dict | None = None
14
14
  """平台的默认配置模板"""
15
- adapter_display_name: str = None
15
+ adapter_display_name: str | None = None
16
16
  """显示在 WebUI 配置页中的平台名称,如空则是 name"""
17
+ logo_path: str | None = None
18
+ """平台适配器的 logo 文件路径(相对于插件目录)"""
19
+
20
+ support_streaming_message: bool = True
21
+ """平台是否支持真实流式传输"""
@@ -1,28 +1,31 @@
1
- from typing import List, Dict, Type
2
- from .platform_metadata import PlatformMetadata
3
1
  from astrbot.core import logger
4
2
 
5
- platform_registry: List[PlatformMetadata] = []
3
+ from .platform_metadata import PlatformMetadata
4
+
5
+ platform_registry: list[PlatformMetadata] = []
6
6
  """维护了通过装饰器注册的平台适配器"""
7
- platform_cls_map: Dict[str, Type] = {}
7
+ platform_cls_map: dict[str, type] = {}
8
8
  """维护了平台适配器名称和适配器类的映射"""
9
9
 
10
10
 
11
11
  def register_platform_adapter(
12
12
  adapter_name: str,
13
13
  desc: str,
14
- default_config_tmpl: dict = None,
15
- adapter_display_name: str = None,
14
+ default_config_tmpl: dict | None = None,
15
+ adapter_display_name: str | None = None,
16
+ logo_path: str | None = None,
17
+ support_streaming_message: bool = True,
16
18
  ):
17
19
  """用于注册平台适配器的带参装饰器。
18
20
 
19
21
  default_config_tmpl 指定了平台适配器的默认配置模板。用户填写好后将会作为 platform_config 传入你的 Platform 类的实现类。
22
+ logo_path 指定了平台适配器的 logo 文件路径,是相对于插件目录的路径。
20
23
  """
21
24
 
22
25
  def decorator(cls):
23
26
  if adapter_name in platform_cls_map:
24
27
  raise ValueError(
25
- f"平台适配器 {adapter_name} 已经注册过了,可能发生了适配器命名冲突。"
28
+ f"平台适配器 {adapter_name} 已经注册过了,可能发生了适配器命名冲突。",
26
29
  )
27
30
 
28
31
  # 添加必备选项
@@ -39,6 +42,8 @@ def register_platform_adapter(
39
42
  description=desc,
40
43
  default_config_tmpl=default_config_tmpl,
41
44
  adapter_display_name=adapter_display_name,
45
+ logo_path=logo_path,
46
+ support_streaming_message=support_streaming_message,
42
47
  )
43
48
  platform_registry.append(pm)
44
49
  platform_cls_map[adapter_name] = cls
@@ -1,90 +1,166 @@
1
1
  import asyncio
2
2
  import re
3
- from typing import AsyncGenerator, Dict, List
4
- from aiocqhttp import CQHttp
3
+ from collections.abc import AsyncGenerator
4
+
5
+ from aiocqhttp import CQHttp, Event
6
+
5
7
  from astrbot.api.event import AstrMessageEvent, MessageChain
6
- from astrbot.api.message_components import At, Image, Node, Nodes, Plain, Record
8
+ from astrbot.api.message_components import (
9
+ BaseMessageComponent,
10
+ File,
11
+ Image,
12
+ Node,
13
+ Nodes,
14
+ Plain,
15
+ Record,
16
+ Video,
17
+ )
7
18
  from astrbot.api.platform import Group, MessageMember
8
19
 
9
20
 
10
21
  class AiocqhttpMessageEvent(AstrMessageEvent):
11
22
  def __init__(
12
- self, message_str, message_obj, platform_meta, session_id, bot: CQHttp
23
+ self,
24
+ message_str,
25
+ message_obj,
26
+ platform_meta,
27
+ session_id,
28
+ bot: CQHttp,
13
29
  ):
14
30
  super().__init__(message_str, message_obj, platform_meta, session_id)
15
31
  self.bot = bot
16
32
 
33
+ @staticmethod
34
+ async def _from_segment_to_dict(segment: BaseMessageComponent) -> dict:
35
+ """修复部分字段"""
36
+ if isinstance(segment, (Image, Record)):
37
+ # For Image and Record segments, we convert them to base64
38
+ bs64 = await segment.convert_to_base64()
39
+ return {
40
+ "type": segment.type.lower(),
41
+ "data": {
42
+ "file": f"base64://{bs64}",
43
+ },
44
+ }
45
+ if isinstance(segment, File):
46
+ # For File segments, we need to handle the file differently
47
+ d = await segment.to_dict()
48
+ return d
49
+ if isinstance(segment, Video):
50
+ d = await segment.to_dict()
51
+ return d
52
+ # For other segments, we simply convert them to a dict by calling toDict
53
+ return segment.toDict()
54
+
17
55
  @staticmethod
18
56
  async def _parse_onebot_json(message_chain: MessageChain):
19
57
  """解析成 OneBot json 格式"""
20
58
  ret = []
21
59
  for segment in message_chain.chain:
22
- d = segment.toDict()
23
60
  if isinstance(segment, Plain):
24
- d["type"] = "text"
25
- d["data"]["text"] = segment.text.strip()
26
- # 如果是空文本或者只带换行符的文本,不发送
27
- if not d["data"]["text"]:
61
+ if not segment.text.strip():
28
62
  continue
29
- elif isinstance(segment, (Image, Record)):
30
- # convert to base64
31
- bs64 = await segment.convert_to_base64()
32
- d["data"] = {
33
- "file": f"base64://{bs64}",
34
- }
35
- elif isinstance(segment, At):
36
- d["data"] = {
37
- "qq": str(segment.qq) # 转换为字符串
38
- }
63
+ d = await AiocqhttpMessageEvent._from_segment_to_dict(segment)
39
64
  ret.append(d)
40
65
  return ret
41
66
 
42
- async def send(self, message: MessageChain):
43
- ret = await AiocqhttpMessageEvent._parse_onebot_json(message)
44
-
45
- if not ret:
67
+ @classmethod
68
+ async def _dispatch_send(
69
+ cls,
70
+ bot: CQHttp,
71
+ event: Event | None,
72
+ is_group: bool,
73
+ session_id: str,
74
+ messages: list[dict],
75
+ ):
76
+ # session_id 必须是纯数字字符串
77
+ session_id = int(session_id) if session_id.isdigit() else None
78
+
79
+ if is_group and isinstance(session_id, int):
80
+ await bot.send_group_msg(group_id=session_id, message=messages)
81
+ elif not is_group and isinstance(session_id, int):
82
+ await bot.send_private_msg(user_id=session_id, message=messages)
83
+ elif isinstance(event, Event): # 最后兜底
84
+ await bot.send(event=event, message=messages)
85
+ else:
86
+ raise ValueError(
87
+ f"无法发送消息:缺少有效的数字 session_id({session_id}) 或 event({event})",
88
+ )
89
+
90
+ @classmethod
91
+ async def send_message(
92
+ cls,
93
+ bot: CQHttp,
94
+ message_chain: MessageChain,
95
+ event: Event | None = None,
96
+ is_group: bool = False,
97
+ session_id: str | None = None,
98
+ ):
99
+ """发送消息至 QQ 协议端(aiocqhttp)。
100
+
101
+ Args:
102
+ bot (CQHttp): aiocqhttp 机器人实例
103
+ message_chain (MessageChain): 要发送的消息链
104
+ event (Event | None, optional): aiocqhttp 事件对象.
105
+ is_group (bool, optional): 是否为群消息.
106
+ session_id (str | None, optional): 会话 ID(群号或 QQ 号
107
+
108
+ """
109
+ # 转发消息、文件消息不能和普通消息混在一起发送
110
+ send_one_by_one = any(
111
+ isinstance(seg, (Node, Nodes, File)) for seg in message_chain.chain
112
+ )
113
+ if not send_one_by_one:
114
+ ret = await cls._parse_onebot_json(message_chain)
115
+ if not ret:
116
+ return
117
+ await cls._dispatch_send(bot, event, is_group, session_id, ret)
46
118
  return
47
-
48
- send_one_by_one = False
49
- for seg in message.chain:
119
+ for seg in message_chain.chain:
50
120
  if isinstance(seg, (Node, Nodes)):
51
- # 转发消息不能和普通消息混在一起发送
52
- send_one_by_one = True
53
- break
54
-
55
- if send_one_by_one:
56
- for seg in message.chain:
57
- if isinstance(seg, (Node, Nodes)):
58
- # 合并转发消息
59
-
60
- if isinstance(seg, Node):
61
- nodes = Nodes([seg])
62
- seg = nodes
63
-
64
- payload = seg.toDict()
65
- if self.get_group_id():
66
- payload["group_id"] = self.get_group_id()
67
- await self.bot.call_action("send_group_forward_msg", **payload)
68
- else:
69
- payload["user_id"] = self.get_sender_id()
70
- await self.bot.call_action(
71
- "send_private_forward_msg", **payload
72
- )
121
+ # 合并转发消息
122
+ if isinstance(seg, Node):
123
+ nodes = Nodes([seg])
124
+ seg = nodes
125
+
126
+ payload = await seg.to_dict()
127
+
128
+ if is_group:
129
+ payload["group_id"] = session_id
130
+ await bot.call_action("send_group_forward_msg", **payload)
73
131
  else:
74
- await self.bot.send(
75
- self.message_obj.raw_message,
76
- await AiocqhttpMessageEvent._parse_onebot_json(
77
- MessageChain([seg])
78
- ),
79
- )
80
- await asyncio.sleep(0.5)
81
- else:
82
- await self.bot.send(self.message_obj.raw_message, ret)
132
+ payload["user_id"] = session_id
133
+ await bot.call_action("send_private_forward_msg", **payload)
134
+ elif isinstance(seg, File):
135
+ d = await cls._from_segment_to_dict(seg)
136
+ await cls._dispatch_send(bot, event, is_group, session_id, [d])
137
+ else:
138
+ messages = await cls._parse_onebot_json(MessageChain([seg]))
139
+ if not messages:
140
+ continue
141
+ await cls._dispatch_send(bot, event, is_group, session_id, messages)
142
+ await asyncio.sleep(0.5)
83
143
 
144
+ async def send(self, message: MessageChain):
145
+ """发送消息"""
146
+ event = getattr(self.message_obj, "raw_message", None)
147
+
148
+ is_group = bool(self.get_group_id())
149
+ session_id = self.get_group_id() if is_group else self.get_sender_id()
150
+
151
+ await self.send_message(
152
+ bot=self.bot,
153
+ message_chain=message,
154
+ event=event, # 不强制要求一定是 Event
155
+ is_group=is_group,
156
+ session_id=session_id,
157
+ )
84
158
  await super().send(message)
85
159
 
86
160
  async def send_streaming(
87
- self, generator: AsyncGenerator, use_fallback: bool = False
161
+ self,
162
+ generator: AsyncGenerator,
163
+ use_fallback: bool = False,
88
164
  ):
89
165
  if not use_fallback:
90
166
  buffer = None
@@ -94,7 +170,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
94
170
  else:
95
171
  buffer.chain.extend(chain.chain)
96
172
  if not buffer:
97
- return
173
+ return None
98
174
  buffer.squash_plain()
99
175
  await self.send(buffer)
100
176
  return await super().send_streaming(generator, use_fallback)
@@ -130,7 +206,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
130
206
  group_id=group_id,
131
207
  )
132
208
 
133
- members: List[Dict] = await self.bot.call_action(
209
+ members: list[dict] = await self.bot.call_action(
134
210
  "get_group_member_list",
135
211
  group_id=group_id,
136
212
  )