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.
- astrbot/api/__init__.py +16 -4
- astrbot/api/all.py +2 -1
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -34
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +8 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__init__.py +1 -0
- astrbot/cli/__main__.py +18 -197
- astrbot/cli/commands/__init__.py +6 -0
- astrbot/cli/commands/cmd_conf.py +209 -0
- astrbot/cli/commands/cmd_init.py +56 -0
- astrbot/cli/commands/cmd_plug.py +245 -0
- astrbot/cli/commands/cmd_run.py +62 -0
- astrbot/cli/utils/__init__.py +18 -0
- astrbot/cli/utils/basic.py +76 -0
- astrbot/cli/utils/plugin.py +246 -0
- astrbot/cli/utils/version_comparator.py +90 -0
- astrbot/core/__init__.py +17 -19
- astrbot/core/agent/agent.py +14 -0
- astrbot/core/agent/handoff.py +38 -0
- astrbot/core/agent/hooks.py +30 -0
- astrbot/core/agent/mcp_client.py +385 -0
- astrbot/core/agent/message.py +175 -0
- astrbot/core/agent/response.py +14 -0
- astrbot/core/agent/run_context.py +22 -0
- astrbot/core/agent/runners/__init__.py +3 -0
- astrbot/core/agent/runners/base.py +65 -0
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
- astrbot/core/agent/tool.py +285 -0
- astrbot/core/agent/tool_executor.py +17 -0
- astrbot/core/astr_agent_context.py +19 -0
- astrbot/core/astr_agent_hooks.py +36 -0
- astrbot/core/astr_agent_run_util.py +80 -0
- astrbot/core/astr_agent_tool_exec.py +246 -0
- astrbot/core/astrbot_config_mgr.py +275 -0
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +60 -20
- astrbot/core/config/default.py +1972 -453
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/conversation_mgr.py +285 -75
- astrbot/core/core_lifecycle.py +167 -62
- astrbot/core/db/__init__.py +305 -102
- astrbot/core/db/migration/helper.py +69 -0
- astrbot/core/db/migration/migra_3_to_4.py +357 -0
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/migration/migra_webchat_session.py +131 -0
- astrbot/core/db/migration/shared_preferences_v3.py +48 -0
- astrbot/core/db/migration/sqlite_v3.py +497 -0
- astrbot/core/db/po.py +259 -55
- astrbot/core/db/sqlite.py +773 -528
- astrbot/core/db/vec_db/base.py +73 -0
- astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
- astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
- astrbot/core/event_bus.py +26 -22
- astrbot/core/exceptions.py +9 -0
- astrbot/core/file_token_service.py +98 -0
- astrbot/core/initial_loader.py +19 -10
- astrbot/core/knowledge_base/chunking/__init__.py +9 -0
- astrbot/core/knowledge_base/chunking/base.py +25 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
- astrbot/core/knowledge_base/chunking/recursive.py +161 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
- astrbot/core/knowledge_base/kb_helper.py +642 -0
- astrbot/core/knowledge_base/kb_mgr.py +330 -0
- astrbot/core/knowledge_base/models.py +120 -0
- astrbot/core/knowledge_base/parsers/__init__.py +13 -0
- astrbot/core/knowledge_base/parsers/base.py +51 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
- astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/prompts.py +65 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +276 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
- astrbot/core/log.py +21 -15
- astrbot/core/message/components.py +413 -287
- astrbot/core/message/message_event_result.py +35 -24
- astrbot/core/persona_mgr.py +192 -0
- astrbot/core/pipeline/__init__.py +14 -14
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +7 -1
- astrbot/core/pipeline/context_utils.py +107 -0
- astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
- astrbot/core/pipeline/process_stage/stage.py +21 -15
- astrbot/core/pipeline/process_stage/utils.py +125 -0
- astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
- astrbot/core/pipeline/respond/stage.py +142 -101
- astrbot/core/pipeline/result_decorate/stage.py +124 -57
- astrbot/core/pipeline/scheduler.py +21 -16
- astrbot/core/pipeline/session_status_check/stage.py +37 -0
- astrbot/core/pipeline/stage.py +11 -76
- astrbot/core/pipeline/waking_check/stage.py +69 -33
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +107 -129
- astrbot/core/platform/astrbot_message.py +32 -12
- astrbot/core/platform/manager.py +62 -18
- astrbot/core/platform/message_session.py +30 -0
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +9 -4
- astrbot/core/platform/register.py +12 -7
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
- astrbot/core/platform/sources/discord/client.py +129 -0
- astrbot/core/platform/sources/discord/components.py +139 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
- astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
- astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
- astrbot/core/platform/sources/lark/lark_event.py +39 -13
- astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
- astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
- astrbot/core/platform/sources/satori/satori_event.py +432 -0
- astrbot/core/platform/sources/slack/client.py +164 -0
- astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
- astrbot/core/platform/sources/slack/slack_event.py +253 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
- astrbot/core/platform/sources/telegram/tg_event.py +136 -36
- astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
- astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
- astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
- astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
- astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
- astrbot/core/platform_message_history_mgr.py +49 -0
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +154 -98
- astrbot/core/provider/func_tool_manager.py +446 -458
- astrbot/core/provider/manager.py +345 -207
- astrbot/core/provider/provider.py +188 -73
- astrbot/core/provider/register.py +9 -7
- astrbot/core/provider/sources/anthropic_source.py +295 -115
- astrbot/core/provider/sources/azure_tts_source.py +224 -0
- astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
- astrbot/core/provider/sources/dashscope_tts.py +138 -14
- astrbot/core/provider/sources/edge_tts_source.py +24 -19
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
- astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
- astrbot/core/provider/sources/gemini_source.py +310 -132
- astrbot/core/provider/sources/gemini_tts_source.py +81 -0
- astrbot/core/provider/sources/groq_source.py +15 -0
- astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
- astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
- astrbot/core/provider/sources/openai_embedding_source.py +40 -0
- astrbot/core/provider/sources/openai_source.py +241 -145
- astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
- astrbot/core/provider/sources/volcengine_tts.py +115 -0
- astrbot/core/provider/sources/whisper_api_source.py +18 -13
- astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/provider/sources/zhipu_source.py +6 -73
- astrbot/core/star/__init__.py +43 -11
- astrbot/core/star/config.py +17 -18
- astrbot/core/star/context.py +362 -138
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +111 -35
- astrbot/core/star/filter/command_group.py +46 -34
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +45 -12
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -15
- astrbot/core/star/register/star.py +41 -13
- astrbot/core/star/register/star_handler.py +236 -86
- astrbot/core/star/session_llm_manager.py +280 -0
- astrbot/core/star/session_plugin_manager.py +170 -0
- astrbot/core/star/star.py +36 -43
- astrbot/core/star/star_handler.py +47 -85
- astrbot/core/star/star_manager.py +442 -260
- astrbot/core/star/star_tools.py +167 -45
- astrbot/core/star/updator.py +17 -20
- astrbot/core/umop_config_router.py +106 -0
- astrbot/core/updator.py +38 -13
- astrbot/core/utils/astrbot_path.py +39 -0
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/core/utils/io.py +119 -60
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +11 -10
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/path_util.py +63 -62
- astrbot/core/utils/pip_installer.py +37 -15
- astrbot/core/utils/session_lock.py +29 -0
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +174 -34
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/local_strategy.py +386 -238
- astrbot/core/utils/t2i/network_strategy.py +109 -49
- astrbot/core/utils/t2i/renderer.py +29 -14
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +111 -0
- astrbot/core/utils/tencent_record_helper.py +115 -1
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +112 -65
- astrbot/dashboard/routes/__init__.py +20 -13
- astrbot/dashboard/routes/auth.py +20 -9
- astrbot/dashboard/routes/chat.py +297 -141
- astrbot/dashboard/routes/config.py +652 -55
- astrbot/dashboard/routes/conversation.py +107 -37
- astrbot/dashboard/routes/file.py +26 -0
- astrbot/dashboard/routes/knowledge_base.py +1244 -0
- astrbot/dashboard/routes/log.py +27 -2
- astrbot/dashboard/routes/persona.py +202 -0
- astrbot/dashboard/routes/plugin.py +197 -139
- astrbot/dashboard/routes/route.py +27 -7
- astrbot/dashboard/routes/session_management.py +354 -0
- astrbot/dashboard/routes/stat.py +85 -18
- astrbot/dashboard/routes/static_file.py +5 -2
- astrbot/dashboard/routes/t2i.py +233 -0
- astrbot/dashboard/routes/tools.py +184 -120
- astrbot/dashboard/routes/update.py +59 -36
- astrbot/dashboard/server.py +96 -36
- astrbot/dashboard/utils.py +165 -0
- astrbot-4.7.0.dist-info/METADATA +294 -0
- astrbot-4.7.0.dist-info/RECORD +274 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
- astrbot/core/db/plugin/sqlite_impl.py +0 -112
- astrbot/core/db/sqlite_init.sql +0 -50
- astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
- astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
- astrbot/core/platform/sources/gewechat/client.py +0 -806
- astrbot/core/platform/sources/gewechat/downloader.py +0 -55
- astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
- astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
- astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
- astrbot/core/provider/sources/dashscope_source.py +0 -203
- astrbot/core/provider/sources/dify_source.py +0 -281
- astrbot/core/provider/sources/llmtuner_source.py +0 -132
- astrbot/core/rag/embedding/openai_source.py +0 -20
- astrbot/core/rag/knowledge_db_mgr.py +0 -94
- astrbot/core/rag/store/__init__.py +0 -9
- astrbot/core/rag/store/chroma_db.py +0 -42
- astrbot/core/utils/dify_api_client.py +0 -152
- astrbot-3.5.6.dist-info/METADATA +0 -249
- astrbot-3.5.6.dist-info/RECORD +0 -158
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import zoneinfo
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from ..utils import check_astrbot_root, get_astrbot_root
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _validate_log_level(value: str) -> str:
|
|
13
|
+
"""验证日志级别"""
|
|
14
|
+
value = value.upper()
|
|
15
|
+
if value not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
|
|
16
|
+
raise click.ClickException(
|
|
17
|
+
"日志级别必须是 DEBUG/INFO/WARNING/ERROR/CRITICAL 之一",
|
|
18
|
+
)
|
|
19
|
+
return value
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _validate_dashboard_port(value: str) -> int:
|
|
23
|
+
"""验证 Dashboard 端口"""
|
|
24
|
+
try:
|
|
25
|
+
port = int(value)
|
|
26
|
+
if port < 1 or port > 65535:
|
|
27
|
+
raise click.ClickException("端口必须在 1-65535 范围内")
|
|
28
|
+
return port
|
|
29
|
+
except ValueError:
|
|
30
|
+
raise click.ClickException("端口必须是数字")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _validate_dashboard_username(value: str) -> str:
|
|
34
|
+
"""验证 Dashboard 用户名"""
|
|
35
|
+
if not value:
|
|
36
|
+
raise click.ClickException("用户名不能为空")
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _validate_dashboard_password(value: str) -> str:
|
|
41
|
+
"""验证 Dashboard 密码"""
|
|
42
|
+
if not value:
|
|
43
|
+
raise click.ClickException("密码不能为空")
|
|
44
|
+
return hashlib.md5(value.encode()).hexdigest()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _validate_timezone(value: str) -> str:
|
|
48
|
+
"""验证时区"""
|
|
49
|
+
try:
|
|
50
|
+
zoneinfo.ZoneInfo(value)
|
|
51
|
+
except Exception:
|
|
52
|
+
raise click.ClickException(f"无效的时区: {value},请使用有效的IANA时区名称")
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _validate_callback_api_base(value: str) -> str:
|
|
57
|
+
"""验证回调接口基址"""
|
|
58
|
+
if not value.startswith("http://") and not value.startswith("https://"):
|
|
59
|
+
raise click.ClickException("回调接口基址必须以 http:// 或 https:// 开头")
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# 可通过CLI设置的配置项,配置键到验证器函数的映射
|
|
64
|
+
CONFIG_VALIDATORS: dict[str, Callable[[str], Any]] = {
|
|
65
|
+
"timezone": _validate_timezone,
|
|
66
|
+
"log_level": _validate_log_level,
|
|
67
|
+
"dashboard.port": _validate_dashboard_port,
|
|
68
|
+
"dashboard.username": _validate_dashboard_username,
|
|
69
|
+
"dashboard.password": _validate_dashboard_password,
|
|
70
|
+
"callback_api_base": _validate_callback_api_base,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _load_config() -> dict[str, Any]:
|
|
75
|
+
"""加载或初始化配置文件"""
|
|
76
|
+
root = get_astrbot_root()
|
|
77
|
+
if not check_astrbot_root(root):
|
|
78
|
+
raise click.ClickException(
|
|
79
|
+
f"{root}不是有效的 AstrBot 根目录,如需初始化请使用 astrbot init",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
config_path = root / "data" / "cmd_config.json"
|
|
83
|
+
if not config_path.exists():
|
|
84
|
+
from astrbot.core.config.default import DEFAULT_CONFIG
|
|
85
|
+
|
|
86
|
+
config_path.write_text(
|
|
87
|
+
json.dumps(DEFAULT_CONFIG, ensure_ascii=False, indent=2),
|
|
88
|
+
encoding="utf-8-sig",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
return json.loads(config_path.read_text(encoding="utf-8-sig"))
|
|
93
|
+
except json.JSONDecodeError as e:
|
|
94
|
+
raise click.ClickException(f"配置文件解析失败: {e!s}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _save_config(config: dict[str, Any]) -> None:
|
|
98
|
+
"""保存配置文件"""
|
|
99
|
+
config_path = get_astrbot_root() / "data" / "cmd_config.json"
|
|
100
|
+
|
|
101
|
+
config_path.write_text(
|
|
102
|
+
json.dumps(config, ensure_ascii=False, indent=2),
|
|
103
|
+
encoding="utf-8-sig",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _set_nested_item(obj: dict[str, Any], path: str, value: Any) -> None:
|
|
108
|
+
"""设置嵌套字典中的值"""
|
|
109
|
+
parts = path.split(".")
|
|
110
|
+
for part in parts[:-1]:
|
|
111
|
+
if part not in obj:
|
|
112
|
+
obj[part] = {}
|
|
113
|
+
elif not isinstance(obj[part], dict):
|
|
114
|
+
raise click.ClickException(
|
|
115
|
+
f"配置路径冲突: {'.'.join(parts[: parts.index(part) + 1])} 不是字典",
|
|
116
|
+
)
|
|
117
|
+
obj = obj[part]
|
|
118
|
+
obj[parts[-1]] = value
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _get_nested_item(obj: dict[str, Any], path: str) -> Any:
|
|
122
|
+
"""获取嵌套字典中的值"""
|
|
123
|
+
parts = path.split(".")
|
|
124
|
+
for part in parts:
|
|
125
|
+
obj = obj[part]
|
|
126
|
+
return obj
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@click.group(name="conf")
|
|
130
|
+
def conf():
|
|
131
|
+
"""配置管理命令
|
|
132
|
+
|
|
133
|
+
支持的配置项:
|
|
134
|
+
|
|
135
|
+
- timezone: 时区设置 (例如: Asia/Shanghai)
|
|
136
|
+
|
|
137
|
+
- log_level: 日志级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
|
138
|
+
|
|
139
|
+
- dashboard.port: Dashboard 端口
|
|
140
|
+
|
|
141
|
+
- dashboard.username: Dashboard 用户名
|
|
142
|
+
|
|
143
|
+
- dashboard.password: Dashboard 密码
|
|
144
|
+
|
|
145
|
+
- callback_api_base: 回调接口基址
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@conf.command(name="set")
|
|
150
|
+
@click.argument("key")
|
|
151
|
+
@click.argument("value")
|
|
152
|
+
def set_config(key: str, value: str):
|
|
153
|
+
"""设置配置项的值"""
|
|
154
|
+
if key not in CONFIG_VALIDATORS:
|
|
155
|
+
raise click.ClickException(f"不支持的配置项: {key}")
|
|
156
|
+
|
|
157
|
+
config = _load_config()
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
old_value = _get_nested_item(config, key)
|
|
161
|
+
validated_value = CONFIG_VALIDATORS[key](value)
|
|
162
|
+
_set_nested_item(config, key, validated_value)
|
|
163
|
+
_save_config(config)
|
|
164
|
+
|
|
165
|
+
click.echo(f"配置已更新: {key}")
|
|
166
|
+
if key == "dashboard.password":
|
|
167
|
+
click.echo(" 原值: ********")
|
|
168
|
+
click.echo(" 新值: ********")
|
|
169
|
+
else:
|
|
170
|
+
click.echo(f" 原值: {old_value}")
|
|
171
|
+
click.echo(f" 新值: {validated_value}")
|
|
172
|
+
|
|
173
|
+
except KeyError:
|
|
174
|
+
raise click.ClickException(f"未知的配置项: {key}")
|
|
175
|
+
except Exception as e:
|
|
176
|
+
raise click.UsageError(f"设置配置失败: {e!s}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@conf.command(name="get")
|
|
180
|
+
@click.argument("key", required=False)
|
|
181
|
+
def get_config(key: str | None = None):
|
|
182
|
+
"""获取配置项的值,不提供key则显示所有可配置项"""
|
|
183
|
+
config = _load_config()
|
|
184
|
+
|
|
185
|
+
if key:
|
|
186
|
+
if key not in CONFIG_VALIDATORS:
|
|
187
|
+
raise click.ClickException(f"不支持的配置项: {key}")
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
value = _get_nested_item(config, key)
|
|
191
|
+
if key == "dashboard.password":
|
|
192
|
+
value = "********"
|
|
193
|
+
click.echo(f"{key}: {value}")
|
|
194
|
+
except KeyError:
|
|
195
|
+
raise click.ClickException(f"未知的配置项: {key}")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
raise click.UsageError(f"获取配置失败: {e!s}")
|
|
198
|
+
else:
|
|
199
|
+
click.echo("当前配置:")
|
|
200
|
+
for key in CONFIG_VALIDATORS:
|
|
201
|
+
try:
|
|
202
|
+
value = (
|
|
203
|
+
"********"
|
|
204
|
+
if key == "dashboard.password"
|
|
205
|
+
else _get_nested_item(config, key)
|
|
206
|
+
)
|
|
207
|
+
click.echo(f" {key}: {value}")
|
|
208
|
+
except (KeyError, TypeError):
|
|
209
|
+
pass
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from filelock import FileLock, Timeout
|
|
6
|
+
|
|
7
|
+
from ..utils import check_dashboard, get_astrbot_root
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def initialize_astrbot(astrbot_root: Path) -> None:
|
|
11
|
+
"""执行 AstrBot 初始化逻辑"""
|
|
12
|
+
dot_astrbot = astrbot_root / ".astrbot"
|
|
13
|
+
|
|
14
|
+
if not dot_astrbot.exists():
|
|
15
|
+
click.echo(f"Current Directory: {astrbot_root}")
|
|
16
|
+
click.echo(
|
|
17
|
+
"如果你确认这是 Astrbot root directory, 你需要在当前目录下创建一个 .astrbot 文件标记该目录为 AstrBot 的数据目录。",
|
|
18
|
+
)
|
|
19
|
+
if click.confirm(
|
|
20
|
+
f"请检查当前目录是否正确,确认正确请回车: {astrbot_root}",
|
|
21
|
+
default=True,
|
|
22
|
+
abort=True,
|
|
23
|
+
):
|
|
24
|
+
dot_astrbot.touch()
|
|
25
|
+
click.echo(f"Created {dot_astrbot}")
|
|
26
|
+
|
|
27
|
+
paths = {
|
|
28
|
+
"data": astrbot_root / "data",
|
|
29
|
+
"config": astrbot_root / "data" / "config",
|
|
30
|
+
"plugins": astrbot_root / "data" / "plugins",
|
|
31
|
+
"temp": astrbot_root / "data" / "temp",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for name, path in paths.items():
|
|
35
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
click.echo(f"{'Created' if not path.exists() else 'Directory exists'}: {path}")
|
|
37
|
+
|
|
38
|
+
await check_dashboard(astrbot_root / "data")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@click.command()
|
|
42
|
+
def init() -> None:
|
|
43
|
+
"""初始化 AstrBot"""
|
|
44
|
+
click.echo("Initializing AstrBot...")
|
|
45
|
+
astrbot_root = get_astrbot_root()
|
|
46
|
+
lock_file = astrbot_root / "astrbot.lock"
|
|
47
|
+
lock = FileLock(lock_file, timeout=5)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
with lock.acquire():
|
|
51
|
+
asyncio.run(initialize_astrbot(astrbot_root))
|
|
52
|
+
except Timeout:
|
|
53
|
+
raise click.ClickException("无法获取锁文件,请检查是否有其他实例正在运行")
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
raise click.ClickException(f"初始化失败: {e!s}")
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..utils import (
|
|
8
|
+
PluginStatus,
|
|
9
|
+
build_plug_list,
|
|
10
|
+
check_astrbot_root,
|
|
11
|
+
get_astrbot_root,
|
|
12
|
+
get_git_repo,
|
|
13
|
+
manage_plugin,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
def plug():
|
|
19
|
+
"""插件管理"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_data_path() -> Path:
|
|
23
|
+
base = get_astrbot_root()
|
|
24
|
+
if not check_astrbot_root(base):
|
|
25
|
+
raise click.ClickException(
|
|
26
|
+
f"{base}不是有效的 AstrBot 根目录,如需初始化请使用 astrbot init",
|
|
27
|
+
)
|
|
28
|
+
return (base / "data").resolve()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def display_plugins(plugins, title=None, color=None):
|
|
32
|
+
if title:
|
|
33
|
+
click.echo(click.style(title, fg=color, bold=True))
|
|
34
|
+
|
|
35
|
+
click.echo(f"{'名称':<20} {'版本':<10} {'状态':<10} {'作者':<15} {'描述':<30}")
|
|
36
|
+
click.echo("-" * 85)
|
|
37
|
+
|
|
38
|
+
for p in plugins:
|
|
39
|
+
desc = p["desc"][:30] + ("..." if len(p["desc"]) > 30 else "")
|
|
40
|
+
click.echo(
|
|
41
|
+
f"{p['name']:<20} {p['version']:<10} {p['status']:<10} "
|
|
42
|
+
f"{p['author']:<15} {desc:<30}",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@plug.command()
|
|
47
|
+
@click.argument("name")
|
|
48
|
+
def new(name: str):
|
|
49
|
+
"""创建新插件"""
|
|
50
|
+
base_path = _get_data_path()
|
|
51
|
+
plug_path = base_path / "plugins" / name
|
|
52
|
+
|
|
53
|
+
if plug_path.exists():
|
|
54
|
+
raise click.ClickException(f"插件 {name} 已存在")
|
|
55
|
+
|
|
56
|
+
author = click.prompt("请输入插件作者", type=str)
|
|
57
|
+
desc = click.prompt("请输入插件描述", type=str)
|
|
58
|
+
version = click.prompt("请输入插件版本", type=str)
|
|
59
|
+
if not re.match(r"^\d+\.\d+(\.\d+)?$", version.lower().lstrip("v")):
|
|
60
|
+
raise click.ClickException("版本号必须为 x.y 或 x.y.z 格式")
|
|
61
|
+
repo = click.prompt("请输入插件仓库:", type=str)
|
|
62
|
+
if not repo.startswith("http"):
|
|
63
|
+
raise click.ClickException("仓库地址必须以 http 开头")
|
|
64
|
+
|
|
65
|
+
click.echo("下载插件模板...")
|
|
66
|
+
get_git_repo(
|
|
67
|
+
"https://github.com/Soulter/helloworld",
|
|
68
|
+
plug_path,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
click.echo("重写插件信息...")
|
|
72
|
+
# 重写 metadata.yaml
|
|
73
|
+
with open(plug_path / "metadata.yaml", "w", encoding="utf-8") as f:
|
|
74
|
+
f.write(
|
|
75
|
+
f"name: {name}\n"
|
|
76
|
+
f"desc: {desc}\n"
|
|
77
|
+
f"version: {version}\n"
|
|
78
|
+
f"author: {author}\n"
|
|
79
|
+
f"repo: {repo}\n",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# 重写 README.md
|
|
83
|
+
with open(plug_path / "README.md", "w", encoding="utf-8") as f:
|
|
84
|
+
f.write(f"# {name}\n\n{desc}\n\n# 支持\n\n[帮助文档](https://astrbot.app)\n")
|
|
85
|
+
|
|
86
|
+
# 重写 main.py
|
|
87
|
+
with open(plug_path / "main.py", encoding="utf-8") as f:
|
|
88
|
+
content = f.read()
|
|
89
|
+
|
|
90
|
+
new_content = content.replace(
|
|
91
|
+
'@register("helloworld", "YourName", "一个简单的 Hello World 插件", "1.0.0")',
|
|
92
|
+
f'@register("{name}", "{author}", "{desc}", "{version}")',
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
with open(plug_path / "main.py", "w", encoding="utf-8") as f:
|
|
96
|
+
f.write(new_content)
|
|
97
|
+
|
|
98
|
+
click.echo(f"插件 {name} 创建成功")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@plug.command()
|
|
102
|
+
@click.option("--all", "-a", is_flag=True, help="列出未安装的插件")
|
|
103
|
+
def list(all: bool):
|
|
104
|
+
"""列出插件"""
|
|
105
|
+
base_path = _get_data_path()
|
|
106
|
+
plugins = build_plug_list(base_path / "plugins")
|
|
107
|
+
|
|
108
|
+
# 未发布的插件
|
|
109
|
+
not_published_plugins = [
|
|
110
|
+
p for p in plugins if p["status"] == PluginStatus.NOT_PUBLISHED
|
|
111
|
+
]
|
|
112
|
+
if not_published_plugins:
|
|
113
|
+
display_plugins(not_published_plugins, "未发布的插件", "red")
|
|
114
|
+
|
|
115
|
+
# 需要更新的插件
|
|
116
|
+
need_update_plugins = [
|
|
117
|
+
p for p in plugins if p["status"] == PluginStatus.NEED_UPDATE
|
|
118
|
+
]
|
|
119
|
+
if need_update_plugins:
|
|
120
|
+
display_plugins(need_update_plugins, "需要更新的插件", "yellow")
|
|
121
|
+
|
|
122
|
+
# 已安装的插件
|
|
123
|
+
installed_plugins = [p for p in plugins if p["status"] == PluginStatus.INSTALLED]
|
|
124
|
+
if installed_plugins:
|
|
125
|
+
display_plugins(installed_plugins, "已安装的插件", "green")
|
|
126
|
+
|
|
127
|
+
# 未安装的插件
|
|
128
|
+
not_installed_plugins = [
|
|
129
|
+
p for p in plugins if p["status"] == PluginStatus.NOT_INSTALLED
|
|
130
|
+
]
|
|
131
|
+
if not_installed_plugins and all:
|
|
132
|
+
display_plugins(not_installed_plugins, "未安装的插件", "blue")
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
not any([not_published_plugins, need_update_plugins, installed_plugins])
|
|
136
|
+
and not all
|
|
137
|
+
):
|
|
138
|
+
click.echo("未安装任何插件")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@plug.command()
|
|
142
|
+
@click.argument("name")
|
|
143
|
+
@click.option("--proxy", help="代理服务器地址")
|
|
144
|
+
def install(name: str, proxy: str | None):
|
|
145
|
+
"""安装插件"""
|
|
146
|
+
base_path = _get_data_path()
|
|
147
|
+
plug_path = base_path / "plugins"
|
|
148
|
+
plugins = build_plug_list(base_path / "plugins")
|
|
149
|
+
|
|
150
|
+
plugin = next(
|
|
151
|
+
(
|
|
152
|
+
p
|
|
153
|
+
for p in plugins
|
|
154
|
+
if p["name"] == name and p["status"] == PluginStatus.NOT_INSTALLED
|
|
155
|
+
),
|
|
156
|
+
None,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if not plugin:
|
|
160
|
+
raise click.ClickException(f"未找到可安装的插件 {name},可能是不存在或已安装")
|
|
161
|
+
|
|
162
|
+
manage_plugin(plugin, plug_path, is_update=False, proxy=proxy)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@plug.command()
|
|
166
|
+
@click.argument("name")
|
|
167
|
+
def remove(name: str):
|
|
168
|
+
"""卸载插件"""
|
|
169
|
+
base_path = _get_data_path()
|
|
170
|
+
plugins = build_plug_list(base_path / "plugins")
|
|
171
|
+
plugin = next((p for p in plugins if p["name"] == name), None)
|
|
172
|
+
|
|
173
|
+
if not plugin or not plugin.get("local_path"):
|
|
174
|
+
raise click.ClickException(f"插件 {name} 不存在或未安装")
|
|
175
|
+
|
|
176
|
+
plugin_path = plugin["local_path"]
|
|
177
|
+
|
|
178
|
+
click.confirm(f"确定要卸载插件 {name} 吗?", default=False, abort=True)
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
shutil.rmtree(plugin_path)
|
|
182
|
+
click.echo(f"插件 {name} 已卸载")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
raise click.ClickException(f"卸载插件 {name} 失败: {e}")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@plug.command()
|
|
188
|
+
@click.argument("name", required=False)
|
|
189
|
+
@click.option("--proxy", help="Github代理地址")
|
|
190
|
+
def update(name: str, proxy: str | None):
|
|
191
|
+
"""更新插件"""
|
|
192
|
+
base_path = _get_data_path()
|
|
193
|
+
plug_path = base_path / "plugins"
|
|
194
|
+
plugins = build_plug_list(base_path / "plugins")
|
|
195
|
+
|
|
196
|
+
if name:
|
|
197
|
+
plugin = next(
|
|
198
|
+
(
|
|
199
|
+
p
|
|
200
|
+
for p in plugins
|
|
201
|
+
if p["name"] == name and p["status"] == PluginStatus.NEED_UPDATE
|
|
202
|
+
),
|
|
203
|
+
None,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if not plugin:
|
|
207
|
+
raise click.ClickException(f"插件 {name} 不需要更新或无法更新")
|
|
208
|
+
|
|
209
|
+
manage_plugin(plugin, plug_path, is_update=True, proxy=proxy)
|
|
210
|
+
else:
|
|
211
|
+
need_update_plugins = [
|
|
212
|
+
p for p in plugins if p["status"] == PluginStatus.NEED_UPDATE
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
if not need_update_plugins:
|
|
216
|
+
click.echo("没有需要更新的插件")
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
click.echo(f"发现 {len(need_update_plugins)} 个插件需要更新")
|
|
220
|
+
for plugin in need_update_plugins:
|
|
221
|
+
plugin_name = plugin["name"]
|
|
222
|
+
click.echo(f"正在更新插件 {plugin_name}...")
|
|
223
|
+
manage_plugin(plugin, plug_path, is_update=True, proxy=proxy)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@plug.command()
|
|
227
|
+
@click.argument("query")
|
|
228
|
+
def search(query: str):
|
|
229
|
+
"""搜索插件"""
|
|
230
|
+
base_path = _get_data_path()
|
|
231
|
+
plugins = build_plug_list(base_path / "plugins")
|
|
232
|
+
|
|
233
|
+
matched_plugins = [
|
|
234
|
+
p
|
|
235
|
+
for p in plugins
|
|
236
|
+
if query.lower() in p["name"].lower()
|
|
237
|
+
or query.lower() in p["desc"].lower()
|
|
238
|
+
or query.lower() in p["author"].lower()
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
if not matched_plugins:
|
|
242
|
+
click.echo(f"未找到匹配 '{query}' 的插件")
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
display_plugins(matched_plugins, f"搜索结果: '{query}'", "cyan")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import traceback
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from filelock import FileLock, Timeout
|
|
9
|
+
|
|
10
|
+
from ..utils import check_astrbot_root, check_dashboard, get_astrbot_root
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def run_astrbot(astrbot_root: Path):
|
|
14
|
+
"""运行 AstrBot"""
|
|
15
|
+
from astrbot.core import LogBroker, LogManager, db_helper, logger
|
|
16
|
+
from astrbot.core.initial_loader import InitialLoader
|
|
17
|
+
|
|
18
|
+
await check_dashboard(astrbot_root / "data")
|
|
19
|
+
|
|
20
|
+
log_broker = LogBroker()
|
|
21
|
+
LogManager.set_queue_handler(logger, log_broker)
|
|
22
|
+
db = db_helper
|
|
23
|
+
|
|
24
|
+
core_lifecycle = InitialLoader(db, log_broker)
|
|
25
|
+
|
|
26
|
+
await core_lifecycle.start()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.option("--reload", "-r", is_flag=True, help="插件自动重载")
|
|
30
|
+
@click.option("--port", "-p", help="Astrbot Dashboard端口", required=False, type=str)
|
|
31
|
+
@click.command()
|
|
32
|
+
def run(reload: bool, port: str) -> None:
|
|
33
|
+
"""运行 AstrBot"""
|
|
34
|
+
try:
|
|
35
|
+
os.environ["ASTRBOT_CLI"] = "1"
|
|
36
|
+
astrbot_root = get_astrbot_root()
|
|
37
|
+
|
|
38
|
+
if not check_astrbot_root(astrbot_root):
|
|
39
|
+
raise click.ClickException(
|
|
40
|
+
f"{astrbot_root}不是有效的 AstrBot 根目录,如需初始化请使用 astrbot init",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
os.environ["ASTRBOT_ROOT"] = str(astrbot_root)
|
|
44
|
+
sys.path.insert(0, str(astrbot_root))
|
|
45
|
+
|
|
46
|
+
if port:
|
|
47
|
+
os.environ["DASHBOARD_PORT"] = port
|
|
48
|
+
|
|
49
|
+
if reload:
|
|
50
|
+
click.echo("启用插件自动重载")
|
|
51
|
+
os.environ["ASTRBOT_RELOAD"] = "1"
|
|
52
|
+
|
|
53
|
+
lock_file = astrbot_root / "astrbot.lock"
|
|
54
|
+
lock = FileLock(lock_file, timeout=5)
|
|
55
|
+
with lock.acquire():
|
|
56
|
+
asyncio.run(run_astrbot(astrbot_root))
|
|
57
|
+
except KeyboardInterrupt:
|
|
58
|
+
click.echo("AstrBot 已关闭...")
|
|
59
|
+
except Timeout:
|
|
60
|
+
raise click.ClickException("无法获取锁文件,请检查是否有其他实例正在运行")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
raise click.ClickException(f"运行时出现错误: {e}\n{traceback.format_exc()}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .basic import (
|
|
2
|
+
check_astrbot_root,
|
|
3
|
+
check_dashboard,
|
|
4
|
+
get_astrbot_root,
|
|
5
|
+
)
|
|
6
|
+
from .plugin import PluginStatus, build_plug_list, get_git_repo, manage_plugin
|
|
7
|
+
from .version_comparator import VersionComparator
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"PluginStatus",
|
|
11
|
+
"VersionComparator",
|
|
12
|
+
"build_plug_list",
|
|
13
|
+
"check_astrbot_root",
|
|
14
|
+
"check_dashboard",
|
|
15
|
+
"get_astrbot_root",
|
|
16
|
+
"get_git_repo",
|
|
17
|
+
"manage_plugin",
|
|
18
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def check_astrbot_root(path: str | Path) -> bool:
|
|
7
|
+
"""检查路径是否为 AstrBot 根目录"""
|
|
8
|
+
if not isinstance(path, Path):
|
|
9
|
+
path = Path(path)
|
|
10
|
+
if not path.exists() or not path.is_dir():
|
|
11
|
+
return False
|
|
12
|
+
if not (path / ".astrbot").exists():
|
|
13
|
+
return False
|
|
14
|
+
return True
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_astrbot_root() -> Path:
|
|
18
|
+
"""获取Astrbot根目录路径"""
|
|
19
|
+
return Path.cwd()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def check_dashboard(astrbot_root: Path) -> None:
|
|
23
|
+
"""检查是否安装了dashboard"""
|
|
24
|
+
from astrbot.core.config.default import VERSION
|
|
25
|
+
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
|
|
26
|
+
|
|
27
|
+
from .version_comparator import VersionComparator
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
dashboard_version = await get_dashboard_version()
|
|
31
|
+
match dashboard_version:
|
|
32
|
+
case None:
|
|
33
|
+
click.echo("未安装管理面板")
|
|
34
|
+
if click.confirm(
|
|
35
|
+
"是否安装管理面板?",
|
|
36
|
+
default=True,
|
|
37
|
+
abort=True,
|
|
38
|
+
):
|
|
39
|
+
click.echo("正在安装管理面板...")
|
|
40
|
+
await download_dashboard(
|
|
41
|
+
path="data/dashboard.zip",
|
|
42
|
+
extract_path=str(astrbot_root),
|
|
43
|
+
version=f"v{VERSION}",
|
|
44
|
+
latest=False,
|
|
45
|
+
)
|
|
46
|
+
click.echo("管理面板安装完成")
|
|
47
|
+
|
|
48
|
+
case str():
|
|
49
|
+
if VersionComparator.compare_version(VERSION, dashboard_version) <= 0:
|
|
50
|
+
click.echo("管理面板已是最新版本")
|
|
51
|
+
return
|
|
52
|
+
try:
|
|
53
|
+
version = dashboard_version.split("v")[1]
|
|
54
|
+
click.echo(f"管理面板版本: {version}")
|
|
55
|
+
await download_dashboard(
|
|
56
|
+
path="data/dashboard.zip",
|
|
57
|
+
extract_path=str(astrbot_root),
|
|
58
|
+
version=f"v{VERSION}",
|
|
59
|
+
latest=False,
|
|
60
|
+
)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
click.echo(f"下载管理面板失败: {e}")
|
|
63
|
+
return
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
click.echo("初始化管理面板目录...")
|
|
66
|
+
try:
|
|
67
|
+
await download_dashboard(
|
|
68
|
+
path=str(astrbot_root / "dashboard.zip"),
|
|
69
|
+
extract_path=str(astrbot_root),
|
|
70
|
+
version=f"v{VERSION}",
|
|
71
|
+
latest=False,
|
|
72
|
+
)
|
|
73
|
+
click.echo("管理面板初始化完成")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
click.echo(f"下载管理面板失败: {e}")
|
|
76
|
+
return
|