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,22 +1,23 @@
1
- import traceback
2
- import aiohttp
1
+ import json
3
2
  import os
4
-
5
3
  import ssl
6
- import certifi
4
+ import traceback
5
+ from datetime import datetime
7
6
 
8
- from .route import Route, Response, RouteContext
9
- from astrbot.core import logger
7
+ import aiohttp
8
+ import certifi
10
9
  from quart import request
11
- from astrbot.core.star.star_manager import PluginManager
10
+
11
+ from astrbot.core import DEMO_MODE, file_token_service, logger
12
12
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
13
- from astrbot.core.star.star_handler import star_handlers_registry
14
13
  from astrbot.core.star.filter.command import CommandFilter
15
14
  from astrbot.core.star.filter.command_group import CommandGroupFilter
16
15
  from astrbot.core.star.filter.permission import PermissionTypeFilter
17
16
  from astrbot.core.star.filter.regex import RegexFilter
18
- from astrbot.core.star.star_handler import EventType
19
- from astrbot.core import DEMO_MODE
17
+ from astrbot.core.star.star_handler import EventType, star_handlers_registry
18
+ from astrbot.core.star.star_manager import PluginManager
19
+
20
+ from .route import Response, Route, RouteContext
20
21
 
21
22
 
22
23
  class PluginRoute(Route):
@@ -38,8 +39,6 @@ class PluginRoute(Route):
38
39
  "/plugin/on": ("POST", self.on_plugin),
39
40
  "/plugin/reload": ("POST", self.reload_plugins),
40
41
  "/plugin/readme": ("GET", self.get_plugin_readme),
41
- "/plugin/platform_enable/get": ("GET", self.get_plugin_platform_enable),
42
- "/plugin/platform_enable/set": ("POST", self.set_plugin_platform_enable),
43
42
  }
44
43
  self.core_lifecycle = core_lifecycle
45
44
  self.plugin_manager = plugin_manager
@@ -54,6 +53,8 @@ class PluginRoute(Route):
54
53
  EventType.OnAfterMessageSentEvent: "发送消息后",
55
54
  }
56
55
 
56
+ self._logo_cache = {}
57
+
57
58
  async def reload_plugins(self):
58
59
  if DEMO_MODE:
59
60
  return (
@@ -75,34 +76,185 @@ class PluginRoute(Route):
75
76
 
76
77
  async def get_online_plugins(self):
77
78
  custom = request.args.get("custom_registry")
79
+ force_refresh = request.args.get("force_refresh", "false").lower() == "true"
80
+
81
+ cache_file = "data/plugins.json"
78
82
 
79
83
  if custom:
80
84
  urls = [custom]
81
85
  else:
82
- urls = ["https://api.soulter.top/astrbot/plugins"]
83
-
84
- # 新增:创建 SSL 上下文,使用 certifi 提供的根证书
86
+ urls = [
87
+ "https://api.soulter.top/astrbot/plugins",
88
+ "https://github.com/AstrBotDevs/AstrBot_Plugins_Collection/raw/refs/heads/main/plugin_cache_original.json",
89
+ ]
90
+
91
+ # 如果不是强制刷新,先检查缓存是否有效
92
+ cached_data = None
93
+ if not force_refresh:
94
+ # 先检查MD5是否匹配,如果匹配则使用缓存
95
+ if await self._is_cache_valid(cache_file):
96
+ cached_data = self._load_plugin_cache(cache_file)
97
+ if cached_data:
98
+ logger.debug("缓存MD5匹配,使用缓存的插件市场数据")
99
+ return Response().ok(cached_data).__dict__
100
+
101
+ # 尝试获取远程数据
102
+ remote_data = None
85
103
  ssl_context = ssl.create_default_context(cafile=certifi.where())
86
104
  connector = aiohttp.TCPConnector(ssl=ssl_context)
105
+
87
106
  for url in urls:
88
107
  try:
89
- async with aiohttp.ClientSession(
90
- trust_env=True, connector=connector
91
- ) as session:
92
- async with session.get(url) as response:
93
- if response.status == 200:
94
- result = await response.json()
95
- return Response().ok(result).__dict__
96
- else:
97
- logger.error(f"请求 {url} 失败,状态码:{response.status}")
108
+ async with (
109
+ aiohttp.ClientSession(
110
+ trust_env=True,
111
+ connector=connector,
112
+ ) as session,
113
+ session.get(url) as response,
114
+ ):
115
+ if response.status == 200:
116
+ remote_data = await response.json()
117
+
118
+ # 检查远程数据是否为空
119
+ if not remote_data or (
120
+ isinstance(remote_data, dict) and len(remote_data) == 0
121
+ ):
122
+ logger.warning(f"远程插件市场数据为空: {url}")
123
+ continue # 继续尝试其他URL或使用缓存
124
+
125
+ logger.info("成功获取远程插件市场数据")
126
+ # 获取最新的MD5并保存到缓存
127
+ current_md5 = await self._get_remote_md5()
128
+ self._save_plugin_cache(
129
+ cache_file,
130
+ remote_data,
131
+ current_md5,
132
+ )
133
+ return Response().ok(remote_data).__dict__
134
+ logger.error(f"请求 {url} 失败,状态码:{response.status}")
98
135
  except Exception as e:
99
136
  logger.error(f"请求 {url} 失败,错误:{e}")
100
137
 
101
- return Response().error("获取插件列表失败").__dict__
138
+ # 如果远程获取失败,尝试使用缓存数据
139
+ if not cached_data:
140
+ cached_data = self._load_plugin_cache(cache_file)
141
+
142
+ if cached_data:
143
+ logger.warning("远程插件市场数据获取失败,使用缓存数据")
144
+ return Response().ok(cached_data, "使用缓存数据,可能不是最新版本").__dict__
145
+
146
+ return Response().error("获取插件列表失败,且没有可用的缓存数据").__dict__
147
+
148
+ async def _is_cache_valid(self, cache_file: str) -> bool:
149
+ """检查缓存是否有效(基于MD5)"""
150
+ try:
151
+ if not os.path.exists(cache_file):
152
+ return False
153
+
154
+ # 加载缓存文件
155
+ with open(cache_file, encoding="utf-8") as f:
156
+ cache_data = json.load(f)
157
+
158
+ cached_md5 = cache_data.get("md5")
159
+ if not cached_md5:
160
+ logger.debug("缓存文件中没有MD5信息")
161
+ return False
162
+
163
+ # 获取远程MD5
164
+ remote_md5 = await self._get_remote_md5()
165
+ if not remote_md5:
166
+ logger.warning("无法获取远程MD5,将使用缓存")
167
+ return True # 如果无法获取远程MD5,认为缓存有效
168
+
169
+ is_valid = cached_md5 == remote_md5
170
+ logger.debug(
171
+ f"插件数据MD5: 本地={cached_md5}, 远程={remote_md5}, 有效={is_valid}",
172
+ )
173
+ return is_valid
174
+
175
+ except Exception as e:
176
+ logger.warning(f"检查缓存有效性失败: {e}")
177
+ return False
178
+
179
+ async def _get_remote_md5(self) -> str:
180
+ """获取远程插件数据的MD5"""
181
+ try:
182
+ ssl_context = ssl.create_default_context(cafile=certifi.where())
183
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
184
+
185
+ async with (
186
+ aiohttp.ClientSession(
187
+ trust_env=True,
188
+ connector=connector,
189
+ ) as session,
190
+ session.get(
191
+ "https://api.soulter.top/astrbot/plugins-md5",
192
+ ) as response,
193
+ ):
194
+ if response.status == 200:
195
+ data = await response.json()
196
+ return data.get("md5", "")
197
+ logger.error(f"获取MD5失败,状态码:{response.status}")
198
+ return ""
199
+ except Exception as e:
200
+ logger.error(f"获取远程MD5失败: {e}")
201
+ return ""
202
+
203
+ def _load_plugin_cache(self, cache_file: str):
204
+ """加载本地缓存的插件市场数据"""
205
+ try:
206
+ if os.path.exists(cache_file):
207
+ with open(cache_file, encoding="utf-8") as f:
208
+ cache_data = json.load(f)
209
+ # 检查缓存是否有效
210
+ if "data" in cache_data and "timestamp" in cache_data:
211
+ logger.debug(
212
+ f"加载缓存文件: {cache_file}, 缓存时间: {cache_data['timestamp']}",
213
+ )
214
+ return cache_data["data"]
215
+ except Exception as e:
216
+ logger.warning(f"加载插件市场缓存失败: {e}")
217
+ return None
218
+
219
+ def _save_plugin_cache(self, cache_file: str, data, md5: str | None = None):
220
+ """保存插件市场数据到本地缓存"""
221
+ try:
222
+ # 确保目录存在
223
+ os.makedirs(os.path.dirname(cache_file), exist_ok=True)
224
+
225
+ cache_data = {
226
+ "timestamp": datetime.now().isoformat(),
227
+ "data": data,
228
+ "md5": md5 or "",
229
+ }
230
+
231
+ with open(cache_file, "w", encoding="utf-8") as f:
232
+ json.dump(cache_data, f, ensure_ascii=False, indent=2)
233
+ logger.debug(f"插件市场数据已缓存到: {cache_file}, MD5: {md5}")
234
+ except Exception as e:
235
+ logger.warning(f"保存插件市场缓存失败: {e}")
236
+
237
+ async def get_plugin_logo_token(self, logo_path: str):
238
+ try:
239
+ if token := self._logo_cache.get(logo_path):
240
+ if not await file_token_service.check_token_expired(token):
241
+ return self._logo_cache[logo_path]
242
+ token = await file_token_service.register_file(logo_path, timeout=300)
243
+ self._logo_cache[logo_path] = token
244
+ return token
245
+ except Exception as e:
246
+ logger.warning(f"获取插件 Logo 失败: {e}")
247
+ return None
102
248
 
103
249
  async def get_plugins(self):
104
250
  _plugin_resp = []
251
+ plugin_name = request.args.get("name")
105
252
  for plugin in self.plugin_manager.context.get_all_stars():
253
+ if plugin_name and plugin.name != plugin_name:
254
+ continue
255
+ logo_url = None
256
+ if plugin.logo_path:
257
+ logo_url = await self.get_plugin_logo_token(plugin.logo_path)
106
258
  _t = {
107
259
  "name": plugin.name,
108
260
  "repo": "" if plugin.repo is None else plugin.repo,
@@ -113,8 +265,10 @@ class PluginRoute(Route):
113
265
  "activated": plugin.activated,
114
266
  "online_vesion": "",
115
267
  "handlers": await self.get_plugin_handlers_info(
116
- plugin.star_handler_full_names
268
+ plugin.star_handler_full_names,
117
269
  ),
270
+ "display_name": plugin.display_name,
271
+ "logo": f"/api/file/{logo_url}" if logo_url else None,
118
272
  }
119
273
  _plugin_resp.append(_t)
120
274
  return (
@@ -130,13 +284,15 @@ class PluginRoute(Route):
130
284
  for handler_full_name in handler_full_names:
131
285
  info = {}
132
286
  handler = star_handlers_registry.star_handlers_map.get(
133
- handler_full_name, None
287
+ handler_full_name,
288
+ None,
134
289
  )
135
290
  if handler is None:
136
291
  continue
137
292
  info["event_type"] = handler.event_type.name
138
293
  info["event_type_h"] = self.translated_event_type.get(
139
- handler.event_type, handler.event_type.name
294
+ handler.event_type,
295
+ handler.event_type.name,
140
296
  )
141
297
  info["handler_full_name"] = handler.handler_full_name
142
298
  info["desc"] = handler.desc
@@ -145,9 +301,7 @@ class PluginRoute(Route):
145
301
  if handler.event_type == EventType.AdapterMessageEvent:
146
302
  # 处理平台适配器消息事件
147
303
  has_admin = False
148
- for (
149
- filter
150
- ) in (
304
+ for filter in (
151
305
  handler.event_filters
152
306
  ): # 正常handler就只有 1~2 个 filter,因此这里时间复杂度不会太高
153
307
  if isinstance(filter, CommandFilter):
@@ -156,29 +310,13 @@ class PluginRoute(Route):
156
310
  f"{filter.parent_command_names[0]} {filter.command_name}"
157
311
  )
158
312
  info["cmd"] = info["cmd"].strip()
159
- if (
160
- self.core_lifecycle.astrbot_config["wake_prefix"]
161
- and len(self.core_lifecycle.astrbot_config["wake_prefix"])
162
- > 0
163
- ):
164
- info["cmd"] = (
165
- f"{self.core_lifecycle.astrbot_config['wake_prefix'][0]}{info['cmd']}"
166
- )
167
313
  elif isinstance(filter, CommandGroupFilter):
168
314
  info["type"] = "指令组"
169
315
  info["cmd"] = filter.get_complete_command_names()[0]
170
316
  info["cmd"] = info["cmd"].strip()
171
317
  info["sub_command"] = filter.print_cmd_tree(
172
- filter.sub_command_filters
318
+ filter.sub_command_filters,
173
319
  )
174
- if (
175
- self.core_lifecycle.astrbot_config["wake_prefix"]
176
- and len(self.core_lifecycle.astrbot_config["wake_prefix"])
177
- > 0
178
- ):
179
- info["cmd"] = (
180
- f"{self.core_lifecycle.astrbot_config['wake_prefix'][0]}{info['cmd']}"
181
- )
182
320
  elif isinstance(filter, RegexFilter):
183
321
  info["type"] = "正则匹配"
184
322
  info["cmd"] = filter.regex_str
@@ -257,9 +395,15 @@ class PluginRoute(Route):
257
395
 
258
396
  post_data = await request.json
259
397
  plugin_name = post_data["name"]
398
+ delete_config = post_data.get("delete_config", False)
399
+ delete_data = post_data.get("delete_data", False)
260
400
  try:
261
401
  logger.info(f"正在卸载插件 {plugin_name}")
262
- await self.plugin_manager.uninstall_plugin(plugin_name)
402
+ await self.plugin_manager.uninstall_plugin(
403
+ plugin_name,
404
+ delete_config=delete_config,
405
+ delete_data=delete_data,
406
+ )
263
407
  logger.info(f"卸载插件 {plugin_name} 成功")
264
408
  return Response().ok(None, "卸载成功").__dict__
265
409
  except Exception as e:
@@ -343,7 +487,8 @@ class PluginRoute(Route):
343
487
  return Response().error(f"插件 {plugin_name} 不存在").__dict__
344
488
 
345
489
  plugin_dir = os.path.join(
346
- self.plugin_manager.plugin_store_path, plugin_obj.root_dir_name
490
+ self.plugin_manager.plugin_store_path,
491
+ plugin_obj.root_dir_name,
347
492
  )
348
493
 
349
494
  if not os.path.isdir(plugin_dir):
@@ -357,7 +502,7 @@ class PluginRoute(Route):
357
502
  return Response().error(f"插件 {plugin_name} 没有README文件").__dict__
358
503
 
359
504
  try:
360
- with open(readme_path, "r", encoding="utf-8") as f:
505
+ with open(readme_path, encoding="utf-8") as f:
361
506
  readme_content = f.read()
362
507
 
363
508
  return (
@@ -367,91 +512,4 @@ class PluginRoute(Route):
367
512
  )
368
513
  except Exception as e:
369
514
  logger.error(f"/api/plugin/readme: {traceback.format_exc()}")
370
- return Response().error(f"读取README文件失败: {str(e)}").__dict__
371
-
372
- async def get_plugin_platform_enable(self):
373
- """获取插件在各平台的可用性配置"""
374
- try:
375
- platform_enable = self.core_lifecycle.astrbot_config.get(
376
- "platform_settings", {}
377
- ).get("plugin_enable", {})
378
-
379
- # 获取所有可用平台
380
- platforms = []
381
-
382
- for platform in self.core_lifecycle.astrbot_config.get("platform", []):
383
- platform_type = platform.get("type", "")
384
- platform_id = platform.get("id", "")
385
-
386
- platforms.append(
387
- {
388
- "name": platform_id, # 使用type作为name,这是系统内部使用的平台名称
389
- "id": platform_id, # 保留id字段以便前端可以显示
390
- "type": platform_type,
391
- "display_name": f"{platform_type}({platform_id})",
392
- }
393
- )
394
-
395
- adjusted_platform_enable = {}
396
- for platform_id, plugins in platform_enable.items():
397
- adjusted_platform_enable[platform_id] = plugins
398
-
399
- # 获取所有插件,包括系统内部插件
400
- plugins = []
401
- for plugin in self.plugin_manager.context.get_all_stars():
402
- plugins.append(
403
- {
404
- "name": plugin.name,
405
- "desc": plugin.desc,
406
- "reserved": plugin.reserved, # 添加reserved标志
407
- }
408
- )
409
-
410
- logger.debug(
411
- f"获取插件平台配置: 原始配置={platform_enable}, 调整后={adjusted_platform_enable}"
412
- )
413
-
414
- return (
415
- Response()
416
- .ok(
417
- {
418
- "platforms": platforms,
419
- "plugins": plugins,
420
- "platform_enable": adjusted_platform_enable,
421
- }
422
- )
423
- .__dict__
424
- )
425
- except Exception as e:
426
- logger.error(f"/api/plugin/platform_enable/get: {traceback.format_exc()}")
427
- return Response().error(str(e)).__dict__
428
-
429
- async def set_plugin_platform_enable(self):
430
- """设置插件在各平台的可用性配置"""
431
- if DEMO_MODE:
432
- return (
433
- Response()
434
- .error("You are not permitted to do this operation in demo mode")
435
- .__dict__
436
- )
437
-
438
- try:
439
- data = await request.json
440
- platform_enable = data.get("platform_enable", {})
441
-
442
- # 更新配置
443
- config = self.core_lifecycle.astrbot_config
444
- platform_settings = config.get("platform_settings", {})
445
- platform_settings["plugin_enable"] = platform_enable
446
- config["platform_settings"] = platform_settings
447
- config.save_config()
448
-
449
- # 更新插件的平台兼容性缓存
450
- await self.plugin_manager.update_all_platform_compatibility()
451
-
452
- logger.info(f"插件平台可用性配置已更新: {platform_enable}")
453
-
454
- return Response().ok(None, "插件平台可用性配置已更新").__dict__
455
- except Exception as e:
456
- logger.error(f"/api/plugin/platform_enable/set: {traceback.format_exc()}")
457
- return Response().error(str(e)).__dict__
515
+ return Response().error(f"读取README文件失败: {e!s}").__dict__
@@ -1,7 +1,9 @@
1
- from astrbot.core.config.astrbot_config import AstrBotConfig
2
1
  from dataclasses import dataclass
2
+
3
3
  from quart import Quart
4
4
 
5
+ from astrbot.core.config.astrbot_config import AstrBotConfig
6
+
5
7
 
6
8
  @dataclass
7
9
  class RouteContext:
@@ -15,23 +17,41 @@ class Route:
15
17
  self.config = context.config
16
18
 
17
19
  def register_routes(self):
18
- for route, (method, func) in self.routes.items():
19
- self.app.add_url_rule(f"/api{route}", view_func=func, methods=[method])
20
+ def _add_rule(path, method, func):
21
+ # 统一添加 /api 前缀
22
+ full_path = f"/api{path}"
23
+ self.app.add_url_rule(full_path, view_func=func, methods=[method])
24
+
25
+ # 兼容字典和列表两种格式
26
+ routes_to_register = (
27
+ self.routes.items() if isinstance(self.routes, dict) else self.routes
28
+ )
29
+
30
+ for route, definition in routes_to_register:
31
+ # 兼容一个路由多个方法
32
+ if isinstance(definition, list):
33
+ for method, func in definition:
34
+ _add_rule(route, method, func)
35
+ else:
36
+ method, func = definition
37
+ _add_rule(route, method, func)
20
38
 
21
39
 
22
40
  @dataclass
23
41
  class Response:
24
- status: str = None
25
- message: str = None
26
- data: dict = None
42
+ status: str | None = None
43
+ message: str | None = None
44
+ data: dict | list | None = None
27
45
 
28
46
  def error(self, message: str):
29
47
  self.status = "error"
30
48
  self.message = message
31
49
  return self
32
50
 
33
- def ok(self, data: dict = {}, message: str = None):
51
+ def ok(self, data: dict | list | None = None, message: str | None = None):
34
52
  self.status = "ok"
53
+ if data is None:
54
+ data = {}
35
55
  self.data = data
36
56
  self.message = message
37
57
  return self