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.
- astrbot/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
- astrbot/core/agent/tool.py +7 -2
- astrbot/core/astr_agent_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +59 -1
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +2 -3
- astrbot/core/db/migration/migra_3_to_4.py +2 -0
- astrbot/core/db/migration/sqlite_v3.py +6 -4
- astrbot/core/db/po.py +16 -15
- astrbot/core/db/sqlite.py +4 -3
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
- astrbot/core/event_bus.py +6 -1
- astrbot/core/knowledge_base/retrieval/manager.py +5 -1
- astrbot/core/log.py +2 -1
- astrbot/core/message/components.py +9 -3
- astrbot/core/persona_mgr.py +2 -2
- astrbot/core/pipeline/content_safety_check/stage.py +1 -1
- astrbot/core/pipeline/context_utils.py +2 -1
- astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
- astrbot/core/pipeline/process_stage/stage.py +1 -1
- astrbot/core/pipeline/respond/stage.py +4 -2
- astrbot/core/pipeline/result_decorate/stage.py +68 -21
- astrbot/core/pipeline/scheduler.py +5 -1
- astrbot/core/pipeline/waking_check/stage.py +10 -0
- astrbot/core/platform/astr_message_event.py +5 -3
- astrbot/core/platform/astrbot_message.py +2 -2
- astrbot/core/platform/manager.py +4 -0
- astrbot/core/platform/platform.py +11 -3
- astrbot/core/platform/platform_metadata.py +1 -1
- astrbot/core/platform/register.py +1 -0
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +9 -5
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +24 -16
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
- astrbot/core/platform/sources/discord/client.py +16 -4
- astrbot/core/platform/sources/discord/components.py +2 -2
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +52 -24
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +177 -19
- astrbot/core/platform/sources/lark/lark_event.py +39 -4
- astrbot/core/platform/sources/lark/server.py +206 -0
- astrbot/core/platform/sources/misskey/misskey_adapter.py +2 -3
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +62 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +13 -7
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +5 -3
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
- astrbot/core/platform/sources/slack/client.py +9 -2
- astrbot/core/platform/sources/slack/slack_adapter.py +15 -9
- astrbot/core/platform/sources/slack/slack_event.py +8 -7
- astrbot/core/platform/sources/telegram/tg_adapter.py +1 -1
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +2 -2
- astrbot/core/platform/sources/webchat/webchat_event.py +2 -2
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +26 -9
- astrbot/core/platform/sources/wecom/wecom_adapter.py +25 -28
- astrbot/core/platform/sources/wecom/wecom_event.py +2 -2
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +30 -25
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +10 -7
- astrbot/core/provider/func_tool_manager.py +3 -3
- astrbot/core/provider/manager.py +130 -74
- astrbot/core/provider/provider.py +12 -1
- astrbot/core/provider/sources/azure_tts_source.py +31 -9
- astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
- astrbot/core/provider/sources/dashscope_tts.py +3 -2
- astrbot/core/provider/sources/edge_tts_source.py +1 -1
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
- astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
- astrbot/core/provider/sources/gemini_source.py +12 -10
- astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
- astrbot/core/provider/sources/openai_embedding_source.py +2 -2
- astrbot/core/provider/sources/openai_source.py +4 -0
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
- astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
- astrbot/core/provider/sources/whisper_api_source.py +1 -1
- astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
- astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
- astrbot/core/star/context.py +2 -2
- astrbot/core/star/register/star_handler.py +22 -5
- astrbot/core/star/star_handler.py +85 -4
- astrbot/core/updator.py +3 -3
- astrbot/core/utils/io.py +1 -1
- astrbot/core/utils/session_waiter.py +17 -10
- astrbot/core/utils/shared_preferences.py +32 -0
- astrbot/core/utils/t2i/__init__.py +2 -2
- astrbot/core/utils/t2i/local_strategy.py +25 -31
- astrbot/core/utils/tencent_record_helper.py +1 -1
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +19 -0
- astrbot/dashboard/routes/chat.py +14 -9
- astrbot/dashboard/routes/config.py +10 -20
- astrbot/dashboard/routes/knowledge_base.py +253 -78
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +1 -1
- astrbot/dashboard/routes/plugin.py +108 -51
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +6 -3
- {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/METADATA +3 -1
- {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/RECORD +104 -103
- {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
- {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
165
|
-
if
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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"
|
|
183
|
-
return
|
|
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.
|
|
207
|
-
|
|
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__
|
astrbot/dashboard/server.py
CHANGED
|
@@ -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
|
|
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.
|
|
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 群组
|