AstrBot 4.8.0__py3-none-any.whl → 4.9.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 (104) hide show
  1. astrbot/cli/__init__.py +1 -1
  2. astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
  3. astrbot/core/agent/tool.py +7 -2
  4. astrbot/core/astr_agent_tool_exec.py +5 -1
  5. astrbot/core/config/astrbot_config.py +4 -0
  6. astrbot/core/config/default.py +59 -1
  7. astrbot/core/core_lifecycle.py +1 -1
  8. astrbot/core/db/__init__.py +2 -3
  9. astrbot/core/db/migration/migra_3_to_4.py +2 -0
  10. astrbot/core/db/migration/sqlite_v3.py +6 -4
  11. astrbot/core/db/po.py +16 -15
  12. astrbot/core/db/sqlite.py +4 -3
  13. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
  14. astrbot/core/event_bus.py +6 -1
  15. astrbot/core/knowledge_base/retrieval/manager.py +5 -1
  16. astrbot/core/log.py +2 -1
  17. astrbot/core/message/components.py +9 -3
  18. astrbot/core/persona_mgr.py +2 -2
  19. astrbot/core/pipeline/content_safety_check/stage.py +1 -1
  20. astrbot/core/pipeline/context_utils.py +2 -1
  21. astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
  22. astrbot/core/pipeline/process_stage/stage.py +1 -1
  23. astrbot/core/pipeline/respond/stage.py +4 -2
  24. astrbot/core/pipeline/result_decorate/stage.py +68 -21
  25. astrbot/core/pipeline/scheduler.py +5 -1
  26. astrbot/core/pipeline/waking_check/stage.py +10 -0
  27. astrbot/core/platform/astr_message_event.py +5 -3
  28. astrbot/core/platform/astrbot_message.py +2 -2
  29. astrbot/core/platform/manager.py +4 -0
  30. astrbot/core/platform/platform.py +11 -3
  31. astrbot/core/platform/platform_metadata.py +1 -1
  32. astrbot/core/platform/register.py +1 -0
  33. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
  34. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +9 -5
  35. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +24 -16
  36. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
  37. astrbot/core/platform/sources/discord/client.py +16 -4
  38. astrbot/core/platform/sources/discord/components.py +2 -2
  39. astrbot/core/platform/sources/discord/discord_platform_adapter.py +52 -24
  40. astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
  41. astrbot/core/platform/sources/lark/lark_adapter.py +177 -19
  42. astrbot/core/platform/sources/lark/lark_event.py +39 -4
  43. astrbot/core/platform/sources/lark/server.py +206 -0
  44. astrbot/core/platform/sources/misskey/misskey_adapter.py +2 -3
  45. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +62 -18
  46. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +13 -7
  47. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +5 -3
  48. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
  49. astrbot/core/platform/sources/slack/client.py +9 -2
  50. astrbot/core/platform/sources/slack/slack_adapter.py +15 -9
  51. astrbot/core/platform/sources/slack/slack_event.py +8 -7
  52. astrbot/core/platform/sources/telegram/tg_adapter.py +1 -1
  53. astrbot/core/platform/sources/telegram/tg_event.py +23 -27
  54. astrbot/core/platform/sources/webchat/webchat_adapter.py +2 -2
  55. astrbot/core/platform/sources/webchat/webchat_event.py +2 -2
  56. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +26 -9
  57. astrbot/core/platform/sources/wecom/wecom_adapter.py +25 -28
  58. astrbot/core/platform/sources/wecom/wecom_event.py +2 -2
  59. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
  60. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +30 -25
  61. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +10 -7
  62. astrbot/core/provider/func_tool_manager.py +3 -3
  63. astrbot/core/provider/manager.py +130 -74
  64. astrbot/core/provider/provider.py +12 -1
  65. astrbot/core/provider/sources/azure_tts_source.py +31 -9
  66. astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
  67. astrbot/core/provider/sources/dashscope_tts.py +3 -2
  68. astrbot/core/provider/sources/edge_tts_source.py +1 -1
  69. astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
  70. astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
  71. astrbot/core/provider/sources/gemini_source.py +12 -10
  72. astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
  73. astrbot/core/provider/sources/openai_embedding_source.py +2 -2
  74. astrbot/core/provider/sources/openai_source.py +4 -0
  75. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
  76. astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
  77. astrbot/core/provider/sources/whisper_api_source.py +1 -1
  78. astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
  79. astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
  80. astrbot/core/star/context.py +2 -2
  81. astrbot/core/star/register/star_handler.py +22 -5
  82. astrbot/core/star/star_handler.py +85 -4
  83. astrbot/core/updator.py +3 -3
  84. astrbot/core/utils/io.py +1 -1
  85. astrbot/core/utils/session_waiter.py +17 -10
  86. astrbot/core/utils/shared_preferences.py +32 -0
  87. astrbot/core/utils/t2i/__init__.py +2 -2
  88. astrbot/core/utils/t2i/local_strategy.py +25 -31
  89. astrbot/core/utils/tencent_record_helper.py +1 -1
  90. astrbot/core/utils/version_comparator.py +6 -3
  91. astrbot/core/utils/webhook_utils.py +19 -0
  92. astrbot/dashboard/routes/chat.py +14 -9
  93. astrbot/dashboard/routes/config.py +10 -20
  94. astrbot/dashboard/routes/knowledge_base.py +253 -78
  95. astrbot/dashboard/routes/log.py +13 -8
  96. astrbot/dashboard/routes/platform.py +1 -1
  97. astrbot/dashboard/routes/plugin.py +108 -51
  98. astrbot/dashboard/routes/route.py +2 -0
  99. astrbot/dashboard/server.py +6 -3
  100. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/METADATA +3 -1
  101. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/RECORD +104 -103
  102. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
  103. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
  104. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,17 @@
1
1
  import asyncio
2
+ import hashlib
2
3
  import json
3
4
  import os
4
5
  import ssl
5
6
  import traceback
7
+ from dataclasses import dataclass
6
8
  from datetime import datetime
7
9
 
8
10
  import aiohttp
9
11
  import certifi
10
12
  from quart import request
11
13
 
14
+ from astrbot.api import sp
12
15
  from astrbot.core import DEMO_MODE, file_token_service, logger
13
16
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
14
17
  from astrbot.core.star.filter.command import CommandFilter
@@ -25,6 +28,13 @@ PLUGIN_UPDATE_CONCURRENCY = (
25
28
  )
26
29
 
27
30
 
31
+ @dataclass
32
+ class RegistrySource:
33
+ urls: list[str]
34
+ cache_file: str
35
+ md5_url: str | None # None means "no remote MD5, always treat cache as stale"
36
+
37
+
28
38
  class PluginRoute(Route):
29
39
  def __init__(
30
40
  self,
@@ -45,6 +55,8 @@ class PluginRoute(Route):
45
55
  "/plugin/on": ("POST", self.on_plugin),
46
56
  "/plugin/reload": ("POST", self.reload_plugins),
47
57
  "/plugin/readme": ("GET", self.get_plugin_readme),
58
+ "/plugin/source/get": ("GET", self.get_custom_source),
59
+ "/plugin/source/save": ("POST", self.save_custom_source),
48
60
  }
49
61
  self.core_lifecycle = core_lifecycle
50
62
  self.plugin_manager = plugin_manager
@@ -84,22 +96,15 @@ class PluginRoute(Route):
84
96
  custom = request.args.get("custom_registry")
85
97
  force_refresh = request.args.get("force_refresh", "false").lower() == "true"
86
98
 
87
- cache_file = "data/plugins.json"
88
-
89
- if custom:
90
- urls = [custom]
91
- else:
92
- urls = [
93
- "https://api.soulter.top/astrbot/plugins",
94
- "https://github.com/AstrBotDevs/AstrBot_Plugins_Collection/raw/refs/heads/main/plugin_cache_original.json",
95
- ]
99
+ # 构建注册表源信息
100
+ source = self._build_registry_source(custom)
96
101
 
97
102
  # 如果不是强制刷新,先检查缓存是否有效
98
103
  cached_data = None
99
104
  if not force_refresh:
100
105
  # 先检查MD5是否匹配,如果匹配则使用缓存
101
- if await self._is_cache_valid(cache_file):
102
- cached_data = self._load_plugin_cache(cache_file)
106
+ if await self._is_cache_valid(source):
107
+ cached_data = self._load_plugin_cache(source.cache_file)
103
108
  if cached_data:
104
109
  logger.debug("缓存MD5匹配,使用缓存的插件市场数据")
105
110
  return Response().ok(cached_data).__dict__
@@ -109,7 +114,7 @@ class PluginRoute(Route):
109
114
  ssl_context = ssl.create_default_context(cafile=certifi.where())
110
115
  connector = aiohttp.TCPConnector(ssl=ssl_context)
111
116
 
112
- for url in urls:
117
+ for url in source.urls:
113
118
  try:
114
119
  async with (
115
120
  aiohttp.ClientSession(
@@ -128,11 +133,13 @@ class PluginRoute(Route):
128
133
  logger.warning(f"远程插件市场数据为空: {url}")
129
134
  continue # 继续尝试其他URL或使用缓存
130
135
 
131
- logger.info("成功获取远程插件市场数据")
136
+ logger.info(
137
+ f"成功获取远程插件市场数据,包含 {len(remote_data)} 个插件"
138
+ )
132
139
  # 获取最新的MD5并保存到缓存
133
- current_md5 = await self._get_remote_md5()
140
+ current_md5 = await self._fetch_remote_md5(source.md5_url)
134
141
  self._save_plugin_cache(
135
- cache_file,
142
+ source.cache_file,
136
143
  remote_data,
137
144
  current_md5,
138
145
  )
@@ -143,7 +150,7 @@ class PluginRoute(Route):
143
150
 
144
151
  # 如果远程获取失败,尝试使用缓存数据
145
152
  if not cached_data:
146
- cached_data = self._load_plugin_cache(cache_file)
153
+ cached_data = self._load_plugin_cache(source.cache_file)
147
154
 
148
155
  if cached_data:
149
156
  logger.warning("远程插件市场数据获取失败,使用缓存数据")
@@ -151,39 +158,47 @@ class PluginRoute(Route):
151
158
 
152
159
  return Response().error("获取插件列表失败,且没有可用的缓存数据").__dict__
153
160
 
154
- async def _is_cache_valid(self, cache_file: str) -> bool:
155
- """检查缓存是否有效(基于MD5)"""
156
- try:
157
- if not os.path.exists(cache_file):
158
- return False
159
-
160
- # 加载缓存文件
161
- with open(cache_file, encoding="utf-8") as f:
162
- cache_data = json.load(f)
161
+ def _build_registry_source(self, custom_url: str | None) -> RegistrySource:
162
+ """构建注册表源信息"""
163
+ if custom_url:
164
+ # 对自定义URL生成一个安全的文件名
165
+ url_hash = hashlib.md5(custom_url.encode()).hexdigest()[:8]
166
+ cache_file = f"data/plugins_custom_{url_hash}.json"
163
167
 
164
- cached_md5 = cache_data.get("md5")
165
- if not cached_md5:
166
- logger.debug("缓存文件中没有MD5信息")
167
- return False
168
+ # 更安全的后缀处理方式
169
+ if custom_url.endswith(".json"):
170
+ md5_url = custom_url[:-5] + "-md5.json"
171
+ else:
172
+ md5_url = custom_url + "-md5.json"
168
173
 
169
- # 获取远程MD5
170
- remote_md5 = await self._get_remote_md5()
171
- if not remote_md5:
172
- logger.warning("无法获取远程MD5,将使用缓存")
173
- return True # 如果无法获取远程MD5,认为缓存有效
174
+ urls = [custom_url]
175
+ else:
176
+ cache_file = "data/plugins.json"
177
+ md5_url = "https://api.soulter.top/astrbot/plugins-md5"
178
+ urls = [
179
+ "https://api.soulter.top/astrbot/plugins",
180
+ "https://github.com/AstrBotDevs/AstrBot_Plugins_Collection/raw/refs/heads/main/plugin_cache_original.json",
181
+ ]
182
+ return RegistrySource(urls=urls, cache_file=cache_file, md5_url=md5_url)
174
183
 
175
- is_valid = cached_md5 == remote_md5
176
- logger.debug(
177
- f"插件数据MD5: 本地={cached_md5}, 远程={remote_md5}, 有效={is_valid}",
178
- )
179
- return is_valid
184
+ def _load_cached_md5(self, cache_file: str) -> str | None:
185
+ """从缓存文件中加载MD5"""
186
+ if not os.path.exists(cache_file):
187
+ return None
180
188
 
189
+ try:
190
+ with open(cache_file, encoding="utf-8") as f:
191
+ cache_data = json.load(f)
192
+ return cache_data.get("md5")
181
193
  except Exception as e:
182
- logger.warning(f"检查缓存有效性失败: {e}")
183
- return False
194
+ logger.warning(f"加载缓存MD5失败: {e}")
195
+ return None
196
+
197
+ async def _fetch_remote_md5(self, md5_url: str | None) -> str | None:
198
+ """获取远程MD5"""
199
+ if not md5_url:
200
+ return None
184
201
 
185
- async def _get_remote_md5(self) -> str:
186
- """获取远程插件数据的MD5"""
187
202
  try:
188
203
  ssl_context = ssl.create_default_context(cafile=certifi.where())
189
204
  connector = aiohttp.TCPConnector(ssl=ssl_context)
@@ -193,18 +208,37 @@ class PluginRoute(Route):
193
208
  trust_env=True,
194
209
  connector=connector,
195
210
  ) as session,
196
- session.get(
197
- "https://api.soulter.top/astrbot/plugins-md5",
198
- ) as response,
211
+ session.get(md5_url) as response,
199
212
  ):
200
213
  if response.status == 200:
201
214
  data = await response.json()
202
215
  return data.get("md5", "")
203
- logger.error(f"获取MD5失败,状态码:{response.status}")
204
- return ""
205
216
  except Exception as e:
206
- logger.error(f"获取远程MD5失败: {e}")
207
- return ""
217
+ logger.debug(f"获取远程MD5失败: {e}")
218
+ return None
219
+
220
+ async def _is_cache_valid(self, source: RegistrySource) -> bool:
221
+ """检查缓存是否有效(基于MD5)"""
222
+ try:
223
+ cached_md5 = self._load_cached_md5(source.cache_file)
224
+ if not cached_md5:
225
+ logger.debug("缓存文件中没有MD5信息")
226
+ return False
227
+
228
+ remote_md5 = await self._fetch_remote_md5(source.md5_url)
229
+ if remote_md5 is None:
230
+ logger.warning("无法获取远程MD5,将使用缓存")
231
+ return True # 如果无法获取远程MD5,认为缓存有效
232
+
233
+ is_valid = cached_md5 == remote_md5
234
+ logger.debug(
235
+ f"插件数据MD5: 本地={cached_md5}, 远程={remote_md5}, 有效={is_valid}",
236
+ )
237
+ return is_valid
238
+
239
+ except Exception as e:
240
+ logger.warning(f"检查缓存有效性失败: {e}")
241
+ return False
208
242
 
209
243
  def _load_plugin_cache(self, cache_file: str):
210
244
  """加载本地缓存的插件市场数据"""
@@ -545,9 +579,13 @@ class PluginRoute(Route):
545
579
  logger.warning(f"插件 {plugin_name} 不存在")
546
580
  return Response().error(f"插件 {plugin_name} 不存在").__dict__
547
581
 
582
+ if not plugin_obj.root_dir_name:
583
+ logger.warning(f"插件 {plugin_name} 目录不存在")
584
+ return Response().error(f"插件 {plugin_name} 目录不存在").__dict__
585
+
548
586
  plugin_dir = os.path.join(
549
587
  self.plugin_manager.plugin_store_path,
550
- plugin_obj.root_dir_name,
588
+ plugin_obj.root_dir_name or "",
551
589
  )
552
590
 
553
591
  if not os.path.isdir(plugin_dir):
@@ -572,3 +610,22 @@ class PluginRoute(Route):
572
610
  except Exception as e:
573
611
  logger.error(f"/api/plugin/readme: {traceback.format_exc()}")
574
612
  return Response().error(f"读取README文件失败: {e!s}").__dict__
613
+
614
+ async def get_custom_source(self):
615
+ """获取自定义插件源"""
616
+ sources = await sp.global_get("custom_plugin_sources", [])
617
+ return Response().ok(sources).__dict__
618
+
619
+ async def save_custom_source(self):
620
+ """保存自定义插件源"""
621
+ try:
622
+ data = await request.get_json()
623
+ sources = data.get("sources", [])
624
+ if not isinstance(sources, list):
625
+ return Response().error("sources fields must be a list").__dict__
626
+
627
+ await sp.global_put("custom_plugin_sources", sources)
628
+ return Response().ok(None, "保存成功").__dict__
629
+ except Exception as e:
630
+ logger.error(f"/api/plugin/source/save: {traceback.format_exc()}")
631
+ return Response().error(str(e)).__dict__
@@ -12,6 +12,8 @@ class RouteContext:
12
12
 
13
13
 
14
14
  class Route:
15
+ routes: list | dict
16
+
15
17
  def __init__(self, context: RouteContext):
16
18
  self.app = context.app
17
19
  self.config = context.config
@@ -2,9 +2,12 @@ import asyncio
2
2
  import logging
3
3
  import os
4
4
  import socket
5
+ from typing import cast
5
6
 
6
7
  import jwt
7
8
  import psutil
9
+ from flask.json.provider import DefaultJSONProvider
10
+ from psutil._common import addr as psutil_addr
8
11
  from quart import Quart, g, jsonify, request
9
12
  from quart.logging import default_handler
10
13
 
@@ -21,7 +24,7 @@ from .routes.route import Response, RouteContext
21
24
  from .routes.session_management import SessionManagementRoute
22
25
  from .routes.t2i import T2iRoute
23
26
 
24
- APP: Quart = None
27
+ APP: Quart
25
28
 
26
29
 
27
30
  class AstrBotDashboard:
@@ -48,7 +51,7 @@ class AstrBotDashboard:
48
51
  self.app.config["MAX_CONTENT_LENGTH"] = (
49
52
  128 * 1024 * 1024
50
53
  ) # 将 Flask 允许的最大上传文件体大小设置为 128 MB
51
- self.app.json.sort_keys = False
54
+ cast(DefaultJSONProvider, self.app.json).sort_keys = False
52
55
  self.app.before_request(self.auth_middleware)
53
56
  # token 用于验证请求
54
57
  logging.getLogger(self.app.name).removeHandler(default_handler)
@@ -147,7 +150,7 @@ class AstrBotDashboard:
147
150
  """获取占用端口的进程详细信息"""
148
151
  try:
149
152
  for conn in psutil.net_connections(kind="inet"):
150
- if conn.laddr.port == port:
153
+ if cast(psutil_addr, conn.laddr).port == port:
151
154
  try:
152
155
  process = psutil.Process(conn.pid)
153
156
  # 获取详细信息
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AstrBot
3
- Version: 4.8.0
3
+ Version: 4.9.0
4
4
  Summary: Easy-to-use multi-platform LLM chatbot and development framework
5
5
  License-File: LICENSE
6
6
  Keywords: Astrbot,Astrbot Module,Astrbot Plugin
@@ -81,6 +81,7 @@ Description-Content-Type: text/markdown
81
81
  <img src="https://img.shields.io/github/v/release/AstrBotDevs/AstrBot?color=76bad9" href="https://github.com/AstrBotDevs/AstrBot/releases/latest">
82
82
  <img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="python">
83
83
  <img src="https://deepwiki.com/badge.svg" href="https://deepwiki.com/AstrBotDevs/AstrBot">
84
+ <a href="https://zread.ai/AstrBotDevs/AstrBot" target="_blank"><img src="https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff" alt="zread"/></a>
84
85
  <a href="https://hub.docker.com/r/soulter/astrbot"><img alt="Docker pull" src="https://img.shields.io/docker/pulls/soulter/astrbot.svg?color=76bad9"/></a>
85
86
  <img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fplugin-num&query=%24.result&suffix=%E4%B8%AA&label=%E6%8F%92%E4%BB%B6%E5%B8%82%E5%9C%BA&cacheSeconds=3600">
86
87
  <img src="https://gitcode.com/Soulter/AstrBot/star/badge.svg" href="https://gitcode.com/Soulter/AstrBot">
@@ -267,6 +268,7 @@ pre-commit install
267
268
  - 3 群:630166526
268
269
  - 5 群:822130018
269
270
  - 6 群:753075035
271
+ - 7 群:743746109
270
272
  - 开发者群:975206796
271
273
 
272
274
  ### Telegram 群组