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
|
@@ -1,29 +1,39 @@
|
|
|
1
|
-
"""
|
|
2
|
-
插件的重载、启停、安装、卸载等操作。
|
|
3
|
-
"""
|
|
1
|
+
"""插件的重载、启停、安装、卸载等操作。"""
|
|
4
2
|
|
|
5
|
-
import
|
|
3
|
+
import asyncio
|
|
6
4
|
import functools
|
|
5
|
+
import inspect
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
7
8
|
import os
|
|
8
9
|
import sys
|
|
9
|
-
import json
|
|
10
10
|
import traceback
|
|
11
|
-
import yaml
|
|
12
|
-
import logging
|
|
13
|
-
import asyncio
|
|
14
11
|
from types import ModuleType
|
|
15
|
-
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
from astrbot.core import logger, pip_installer, sp
|
|
16
|
+
from astrbot.core.agent.handoff import FunctionTool, HandoffTool
|
|
16
17
|
from astrbot.core.config.astrbot_config import AstrBotConfig
|
|
17
|
-
from astrbot.core import
|
|
18
|
-
from .
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
from astrbot.core.provider.register import llm_tools
|
|
19
|
+
from astrbot.core.utils.astrbot_path import (
|
|
20
|
+
get_astrbot_config_path,
|
|
21
|
+
get_astrbot_plugin_path,
|
|
22
|
+
)
|
|
21
23
|
from astrbot.core.utils.io import remove_dir
|
|
22
|
-
|
|
24
|
+
|
|
25
|
+
from . import StarMetadata
|
|
26
|
+
from .context import Context
|
|
27
|
+
from .filter.permission import PermissionType, PermissionTypeFilter
|
|
28
|
+
from .star import star_map, star_registry
|
|
23
29
|
from .star_handler import star_handlers_registry
|
|
24
|
-
from
|
|
30
|
+
from .updator import PluginUpdator
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
try:
|
|
33
|
+
from watchfiles import PythonFilter, awatch
|
|
34
|
+
except ImportError:
|
|
35
|
+
if os.getenv("ASTRBOT_RELOAD", "0") == "1":
|
|
36
|
+
logger.warning("未安装 watchfiles,无法实现插件的热重载。")
|
|
27
37
|
|
|
28
38
|
|
|
29
39
|
class PluginManager:
|
|
@@ -31,33 +41,84 @@ class PluginManager:
|
|
|
31
41
|
self.updator = PluginUpdator()
|
|
32
42
|
|
|
33
43
|
self.context = context
|
|
34
|
-
self.context._star_manager = self
|
|
44
|
+
self.context._star_manager = self # type: ignore
|
|
35
45
|
|
|
36
46
|
self.config = config
|
|
37
|
-
self.plugin_store_path =
|
|
38
|
-
os.path.join(
|
|
39
|
-
os.path.dirname(os.path.abspath(__file__)), "../../../data/plugins"
|
|
40
|
-
)
|
|
41
|
-
)
|
|
47
|
+
self.plugin_store_path = get_astrbot_plugin_path()
|
|
42
48
|
"""存储插件的路径。即 data/plugins"""
|
|
43
|
-
self.plugin_config_path =
|
|
44
|
-
os.path.join(
|
|
45
|
-
os.path.dirname(os.path.abspath(__file__)), "../../../data/config"
|
|
46
|
-
)
|
|
47
|
-
)
|
|
49
|
+
self.plugin_config_path = get_astrbot_config_path()
|
|
48
50
|
"""存储插件配置的路径。data/config"""
|
|
49
51
|
self.reserved_plugin_path = os.path.abspath(
|
|
50
52
|
os.path.join(
|
|
51
|
-
os.path.dirname(os.path.abspath(__file__)),
|
|
52
|
-
|
|
53
|
+
os.path.dirname(os.path.abspath(__file__)),
|
|
54
|
+
"../../../packages",
|
|
55
|
+
),
|
|
53
56
|
)
|
|
54
57
|
"""保留插件的路径。在 packages 目录下"""
|
|
55
58
|
self.conf_schema_fname = "_conf_schema.json"
|
|
59
|
+
self.logo_fname = "logo.png"
|
|
56
60
|
"""插件配置 Schema 文件名"""
|
|
61
|
+
self._pm_lock = asyncio.Lock()
|
|
62
|
+
"""StarManager操作互斥锁"""
|
|
57
63
|
|
|
58
64
|
self.failed_plugin_info = ""
|
|
65
|
+
if os.getenv("ASTRBOT_RELOAD", "0") == "1":
|
|
66
|
+
asyncio.create_task(self._watch_plugins_changes())
|
|
67
|
+
|
|
68
|
+
async def _watch_plugins_changes(self):
|
|
69
|
+
"""监视插件文件变化"""
|
|
70
|
+
try:
|
|
71
|
+
async for changes in awatch(
|
|
72
|
+
self.plugin_store_path,
|
|
73
|
+
self.reserved_plugin_path,
|
|
74
|
+
watch_filter=PythonFilter(),
|
|
75
|
+
recursive=True,
|
|
76
|
+
):
|
|
77
|
+
# 处理文件变化
|
|
78
|
+
await self._handle_file_changes(changes)
|
|
79
|
+
except asyncio.CancelledError:
|
|
80
|
+
pass
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"插件热重载监视任务异常: {e!s}")
|
|
83
|
+
logger.error(traceback.format_exc())
|
|
84
|
+
|
|
85
|
+
async def _handle_file_changes(self, changes):
|
|
86
|
+
"""处理文件变化"""
|
|
87
|
+
logger.info(f"检测到文件变化: {changes}")
|
|
88
|
+
plugins_to_check = []
|
|
89
|
+
|
|
90
|
+
for star in star_registry:
|
|
91
|
+
if not star.activated:
|
|
92
|
+
continue
|
|
93
|
+
if star.root_dir_name is None:
|
|
94
|
+
continue
|
|
95
|
+
if star.reserved:
|
|
96
|
+
plugin_dir_path = os.path.join(
|
|
97
|
+
self.reserved_plugin_path,
|
|
98
|
+
star.root_dir_name,
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
plugin_dir_path = os.path.join(
|
|
102
|
+
self.plugin_store_path,
|
|
103
|
+
star.root_dir_name,
|
|
104
|
+
)
|
|
105
|
+
plugins_to_check.append((plugin_dir_path, star.name))
|
|
106
|
+
reloaded_plugins = set()
|
|
107
|
+
for change in changes:
|
|
108
|
+
_, file_path = change
|
|
109
|
+
for plugin_dir_path, plugin_name in plugins_to_check:
|
|
110
|
+
if (
|
|
111
|
+
os.path.commonpath([plugin_dir_path])
|
|
112
|
+
== os.path.commonpath([plugin_dir_path, file_path])
|
|
113
|
+
and plugin_name not in reloaded_plugins
|
|
114
|
+
):
|
|
115
|
+
logger.info(f"检测到插件 {plugin_name} 文件变化,正在重载...")
|
|
116
|
+
await self.reload(plugin_name)
|
|
117
|
+
reloaded_plugins.add(plugin_name)
|
|
118
|
+
break
|
|
59
119
|
|
|
60
|
-
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _get_classes(arg: ModuleType):
|
|
61
122
|
"""获取指定模块(可以理解为一个 python 文件)下所有的类"""
|
|
62
123
|
classes = []
|
|
63
124
|
clsmembers = inspect.getmembers(arg, inspect.isclass)
|
|
@@ -67,7 +128,8 @@ class PluginManager:
|
|
|
67
128
|
break
|
|
68
129
|
return classes
|
|
69
130
|
|
|
70
|
-
|
|
131
|
+
@staticmethod
|
|
132
|
+
def _get_modules(path):
|
|
71
133
|
modules = []
|
|
72
134
|
|
|
73
135
|
dirs = os.listdir(path)
|
|
@@ -82,18 +144,18 @@ class PluginManager:
|
|
|
82
144
|
logger.info(f"插件 {d} 未找到 main.py 或者 {d}.py,跳过。")
|
|
83
145
|
continue
|
|
84
146
|
if os.path.exists(os.path.join(path, d, "main.py")) or os.path.exists(
|
|
85
|
-
os.path.join(path, d, d + ".py")
|
|
147
|
+
os.path.join(path, d, d + ".py"),
|
|
86
148
|
):
|
|
87
149
|
modules.append(
|
|
88
150
|
{
|
|
89
151
|
"pname": d,
|
|
90
152
|
"module": module_str,
|
|
91
153
|
"module_path": os.path.join(path, d, module_str),
|
|
92
|
-
}
|
|
154
|
+
},
|
|
93
155
|
)
|
|
94
156
|
return modules
|
|
95
157
|
|
|
96
|
-
def _get_plugin_modules(self) ->
|
|
158
|
+
def _get_plugin_modules(self) -> list[dict]:
|
|
97
159
|
plugins = []
|
|
98
160
|
if os.path.exists(self.plugin_store_path):
|
|
99
161
|
plugins.extend(self._get_modules(self.plugin_store_path))
|
|
@@ -104,7 +166,7 @@ class PluginManager:
|
|
|
104
166
|
plugins.extend(_p)
|
|
105
167
|
return plugins
|
|
106
168
|
|
|
107
|
-
def _check_plugin_dept_update(self, target_plugin: str = None):
|
|
169
|
+
async def _check_plugin_dept_update(self, target_plugin: str | None = None):
|
|
108
170
|
"""检查插件的依赖
|
|
109
171
|
如果 target_plugin 为 None,则检查所有插件的依赖
|
|
110
172
|
"""
|
|
@@ -123,14 +185,15 @@ class PluginManager:
|
|
|
123
185
|
pth = os.path.join(plugin_path, "requirements.txt")
|
|
124
186
|
logger.info(f"正在安装插件 {p} 所需的依赖库: {pth}")
|
|
125
187
|
try:
|
|
126
|
-
pip_installer.install(requirements_path=pth)
|
|
188
|
+
await pip_installer.install(requirements_path=pth)
|
|
127
189
|
except Exception as e:
|
|
128
|
-
logger.error(f"更新插件 {p} 的依赖失败。Code: {
|
|
190
|
+
logger.error(f"更新插件 {p} 的依赖失败。Code: {e!s}")
|
|
129
191
|
|
|
130
|
-
|
|
131
|
-
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _load_plugin_metadata(plugin_path: str, plugin_obj=None) -> StarMetadata | None:
|
|
194
|
+
"""先寻找 metadata.yaml 文件,如果不存在,则使用插件对象的 info() 函数获取元数据。
|
|
132
195
|
|
|
133
|
-
|
|
196
|
+
Notes: 旧版本 AstrBot 插件可能使用的是 info() 函数来获取元数据。
|
|
134
197
|
"""
|
|
135
198
|
metadata = None
|
|
136
199
|
|
|
@@ -139,14 +202,18 @@ class PluginManager:
|
|
|
139
202
|
|
|
140
203
|
if os.path.exists(os.path.join(plugin_path, "metadata.yaml")):
|
|
141
204
|
with open(
|
|
142
|
-
os.path.join(plugin_path, "metadata.yaml"),
|
|
205
|
+
os.path.join(plugin_path, "metadata.yaml"),
|
|
206
|
+
encoding="utf-8",
|
|
143
207
|
) as f:
|
|
144
208
|
metadata = yaml.safe_load(f)
|
|
145
|
-
elif plugin_obj:
|
|
209
|
+
elif plugin_obj and hasattr(plugin_obj, "info"):
|
|
146
210
|
# 使用 info() 函数
|
|
147
211
|
metadata = plugin_obj.info()
|
|
148
212
|
|
|
149
213
|
if isinstance(metadata, dict):
|
|
214
|
+
if "desc" not in metadata and "description" in metadata:
|
|
215
|
+
metadata["desc"] = metadata["description"]
|
|
216
|
+
|
|
150
217
|
if (
|
|
151
218
|
"name" not in metadata
|
|
152
219
|
or "desc" not in metadata
|
|
@@ -154,7 +221,7 @@ class PluginManager:
|
|
|
154
221
|
or "author" not in metadata
|
|
155
222
|
):
|
|
156
223
|
raise Exception(
|
|
157
|
-
"插件元数据信息不完整。name, desc, version, author 是必须的字段。"
|
|
224
|
+
"插件元数据信息不完整。name, desc, version, author 是必须的字段。",
|
|
158
225
|
)
|
|
159
226
|
metadata = StarMetadata(
|
|
160
227
|
name=metadata["name"],
|
|
@@ -162,12 +229,15 @@ class PluginManager:
|
|
|
162
229
|
desc=metadata["desc"],
|
|
163
230
|
version=metadata["version"],
|
|
164
231
|
repo=metadata["repo"] if "repo" in metadata else None,
|
|
232
|
+
display_name=metadata.get("display_name", None),
|
|
165
233
|
)
|
|
166
234
|
|
|
167
235
|
return metadata
|
|
168
236
|
|
|
237
|
+
@staticmethod
|
|
169
238
|
def _get_plugin_related_modules(
|
|
170
|
-
|
|
239
|
+
plugin_root_dir: str,
|
|
240
|
+
is_reserved: bool = False,
|
|
171
241
|
) -> list[str]:
|
|
172
242
|
"""获取与指定插件相关的所有已加载模块名
|
|
173
243
|
|
|
@@ -179,6 +249,7 @@ class PluginManager:
|
|
|
179
249
|
|
|
180
250
|
Returns:
|
|
181
251
|
list[str]: 与该插件相关的模块名列表
|
|
252
|
+
|
|
182
253
|
"""
|
|
183
254
|
prefix = "packages." if is_reserved else "data.plugins."
|
|
184
255
|
return [
|
|
@@ -189,8 +260,8 @@ class PluginManager:
|
|
|
189
260
|
|
|
190
261
|
def _purge_modules(
|
|
191
262
|
self,
|
|
192
|
-
module_patterns: list[str] = None,
|
|
193
|
-
root_dir_name: str = None,
|
|
263
|
+
module_patterns: list[str] | None = None,
|
|
264
|
+
root_dir_name: str | None = None,
|
|
194
265
|
is_reserved: bool = False,
|
|
195
266
|
):
|
|
196
267
|
"""从 sys.modules 中移除指定的模块
|
|
@@ -201,6 +272,7 @@ class PluginManager:
|
|
|
201
272
|
module_patterns: 要移除的模块名模式列表(例如 ["data.plugins", "packages"])
|
|
202
273
|
root_dir_name: 插件根目录名,用于移除与该插件相关的所有模块
|
|
203
274
|
is_reserved: 插件是否为保留插件(影响模块路径前缀)
|
|
275
|
+
|
|
204
276
|
"""
|
|
205
277
|
if module_patterns:
|
|
206
278
|
for pattern in module_patterns:
|
|
@@ -211,7 +283,8 @@ class PluginManager:
|
|
|
211
283
|
|
|
212
284
|
if root_dir_name:
|
|
213
285
|
for module_name in self._get_plugin_related_modules(
|
|
214
|
-
root_dir_name,
|
|
286
|
+
root_dir_name,
|
|
287
|
+
is_reserved,
|
|
215
288
|
):
|
|
216
289
|
try:
|
|
217
290
|
del sys.modules[module_name]
|
|
@@ -230,70 +303,50 @@ class PluginManager:
|
|
|
230
303
|
tuple: 返回 load() 方法的结果,包含 (success, error_message)
|
|
231
304
|
- success (bool): 重载是否成功
|
|
232
305
|
- error_message (str|None): 错误信息,成功时为 None
|
|
233
|
-
"""
|
|
234
|
-
specified_module_path = None
|
|
235
|
-
if specified_plugin_name:
|
|
236
|
-
for smd in star_registry:
|
|
237
|
-
if smd.name == specified_plugin_name:
|
|
238
|
-
specified_module_path = smd.module_path
|
|
239
|
-
break
|
|
240
|
-
|
|
241
|
-
# 终止插件
|
|
242
|
-
if not specified_module_path:
|
|
243
|
-
# 重载所有插件
|
|
244
|
-
for smd in star_registry:
|
|
245
|
-
try:
|
|
246
|
-
await self._terminate_plugin(smd)
|
|
247
|
-
except Exception as e:
|
|
248
|
-
logger.warning(traceback.format_exc())
|
|
249
|
-
logger.warning(
|
|
250
|
-
f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。"
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
await self._unbind_plugin(smd.name, smd.module_path)
|
|
254
|
-
|
|
255
|
-
star_handlers_registry.clear()
|
|
256
|
-
star_map.clear()
|
|
257
|
-
star_registry.clear()
|
|
258
|
-
else:
|
|
259
|
-
# 只重载指定插件
|
|
260
|
-
smd = star_map.get(specified_module_path)
|
|
261
|
-
if smd:
|
|
262
|
-
try:
|
|
263
|
-
await self._terminate_plugin(smd)
|
|
264
|
-
except Exception as e:
|
|
265
|
-
logger.warning(traceback.format_exc())
|
|
266
|
-
logger.warning(
|
|
267
|
-
f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。"
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
await self._unbind_plugin(smd.name, specified_module_path)
|
|
271
306
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
307
|
+
"""
|
|
308
|
+
async with self._pm_lock:
|
|
309
|
+
specified_module_path = None
|
|
310
|
+
if specified_plugin_name:
|
|
311
|
+
for smd in star_registry:
|
|
312
|
+
if smd.name == specified_plugin_name:
|
|
313
|
+
specified_module_path = smd.module_path
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
# 终止插件
|
|
317
|
+
if not specified_module_path:
|
|
318
|
+
# 重载所有插件
|
|
319
|
+
for smd in star_registry:
|
|
320
|
+
try:
|
|
321
|
+
await self._terminate_plugin(smd)
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.warning(traceback.format_exc())
|
|
324
|
+
logger.warning(
|
|
325
|
+
f"插件 {smd.name} 未被正常终止: {e!s}, 可能会导致该插件运行不正常。",
|
|
326
|
+
)
|
|
327
|
+
if smd.name and smd.module_path:
|
|
328
|
+
await self._unbind_plugin(smd.name, smd.module_path)
|
|
329
|
+
|
|
330
|
+
star_handlers_registry.clear()
|
|
331
|
+
star_map.clear()
|
|
332
|
+
star_registry.clear()
|
|
333
|
+
else:
|
|
334
|
+
# 只重载指定插件
|
|
335
|
+
smd = star_map.get(specified_module_path)
|
|
336
|
+
if smd:
|
|
337
|
+
try:
|
|
338
|
+
await self._terminate_plugin(smd)
|
|
339
|
+
except Exception as e:
|
|
340
|
+
logger.warning(traceback.format_exc())
|
|
341
|
+
logger.warning(
|
|
342
|
+
f"插件 {smd.name} 未被正常终止: {e!s}, 可能会导致该插件运行不正常。",
|
|
343
|
+
)
|
|
344
|
+
if smd.name:
|
|
345
|
+
await self._unbind_plugin(smd.name, specified_module_path)
|
|
288
346
|
|
|
289
|
-
|
|
290
|
-
for plugin in self.context.get_all_stars():
|
|
291
|
-
plugin.update_platform_compatibility(plugin_enable_config)
|
|
292
|
-
logger.debug(
|
|
293
|
-
f"插件 {plugin.name} 支持的平台: {list(plugin.supported_platforms.keys())}"
|
|
294
|
-
)
|
|
347
|
+
result = await self.load(specified_module_path)
|
|
295
348
|
|
|
296
|
-
|
|
349
|
+
return result
|
|
297
350
|
|
|
298
351
|
async def load(self, specified_module_path=None, specified_dir_name=None):
|
|
299
352
|
"""载入插件。
|
|
@@ -307,11 +360,11 @@ class PluginManager:
|
|
|
307
360
|
tuple: (success, error_message)
|
|
308
361
|
- success (bool): 是否全部加载成功
|
|
309
362
|
- error_message (str|None): 错误信息,成功时为 None
|
|
310
|
-
"""
|
|
311
|
-
inactivated_plugins: list = sp.get("inactivated_plugins", [])
|
|
312
|
-
inactivated_llm_tools: list = sp.get("inactivated_llm_tools", [])
|
|
313
363
|
|
|
314
|
-
|
|
364
|
+
"""
|
|
365
|
+
inactivated_plugins = await sp.global_get("inactivated_plugins", [])
|
|
366
|
+
inactivated_llm_tools = await sp.global_get("inactivated_llm_tools", [])
|
|
367
|
+
alter_cmd = await sp.global_get("alter_cmd", {})
|
|
315
368
|
|
|
316
369
|
plugin_modules = self._get_plugin_modules()
|
|
317
370
|
if plugin_modules is None:
|
|
@@ -326,7 +379,8 @@ class PluginManager:
|
|
|
326
379
|
# module_path = plugin_module['module_path']
|
|
327
380
|
root_dir_name = plugin_module["pname"] # 插件的目录名
|
|
328
381
|
reserved = plugin_module.get(
|
|
329
|
-
"reserved",
|
|
382
|
+
"reserved",
|
|
383
|
+
False,
|
|
330
384
|
) # 是否是保留插件。目前在 packages/ 目录下的都是保留插件。保留插件不可以卸载。
|
|
331
385
|
|
|
332
386
|
path = "data.plugins." if not reserved else "packages."
|
|
@@ -345,11 +399,11 @@ class PluginManager:
|
|
|
345
399
|
module = __import__(path, fromlist=[module_str])
|
|
346
400
|
except (ModuleNotFoundError, ImportError):
|
|
347
401
|
# 尝试安装依赖
|
|
348
|
-
self._check_plugin_dept_update(target_plugin=root_dir_name)
|
|
402
|
+
await self._check_plugin_dept_update(target_plugin=root_dir_name)
|
|
349
403
|
module = __import__(path, fromlist=[module_str])
|
|
350
404
|
except Exception as e:
|
|
351
405
|
logger.error(traceback.format_exc())
|
|
352
|
-
logger.error(f"插件 {root_dir_name} 导入失败。原因:{
|
|
406
|
+
logger.error(f"插件 {root_dir_name} 导入失败。原因:{e!s}")
|
|
353
407
|
continue
|
|
354
408
|
|
|
355
409
|
# 检查 _conf_schema.json
|
|
@@ -360,26 +414,29 @@ class PluginManager:
|
|
|
360
414
|
else os.path.join(self.reserved_plugin_path, root_dir_name)
|
|
361
415
|
)
|
|
362
416
|
plugin_schema_path = os.path.join(
|
|
363
|
-
plugin_dir_path,
|
|
417
|
+
plugin_dir_path,
|
|
418
|
+
self.conf_schema_fname,
|
|
364
419
|
)
|
|
365
420
|
if os.path.exists(plugin_schema_path):
|
|
366
421
|
# 加载插件配置
|
|
367
|
-
with open(plugin_schema_path,
|
|
422
|
+
with open(plugin_schema_path, encoding="utf-8") as f:
|
|
368
423
|
plugin_config = AstrBotConfig(
|
|
369
424
|
config_path=os.path.join(
|
|
370
|
-
self.plugin_config_path,
|
|
425
|
+
self.plugin_config_path,
|
|
426
|
+
f"{root_dir_name}_config.json",
|
|
371
427
|
),
|
|
372
428
|
schema=json.loads(f.read()),
|
|
373
429
|
)
|
|
430
|
+
logo_path = os.path.join(plugin_dir_path, self.logo_fname)
|
|
374
431
|
|
|
375
432
|
if path in star_map:
|
|
376
|
-
#
|
|
433
|
+
# 通过 __init__subclass__ 注册插件
|
|
377
434
|
metadata = star_map[path]
|
|
378
435
|
|
|
379
436
|
try:
|
|
380
437
|
# yaml 文件的元数据优先
|
|
381
438
|
metadata_yaml = self._load_plugin_metadata(
|
|
382
|
-
plugin_path=plugin_dir_path
|
|
439
|
+
plugin_path=plugin_dir_path,
|
|
383
440
|
)
|
|
384
441
|
if metadata_yaml:
|
|
385
442
|
metadata.name = metadata_yaml.name
|
|
@@ -387,24 +444,28 @@ class PluginManager:
|
|
|
387
444
|
metadata.desc = metadata_yaml.desc
|
|
388
445
|
metadata.version = metadata_yaml.version
|
|
389
446
|
metadata.repo = metadata_yaml.repo
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
447
|
+
metadata.display_name = metadata_yaml.display_name
|
|
448
|
+
except Exception as e:
|
|
449
|
+
logger.warning(
|
|
450
|
+
f"插件 {root_dir_name} 元数据载入失败: {e!s}。使用默认元数据。",
|
|
451
|
+
)
|
|
452
|
+
logger.info(metadata)
|
|
453
|
+
metadata.config = plugin_config
|
|
393
454
|
if path not in inactivated_plugins:
|
|
394
455
|
# 只有没有禁用插件时才实例化插件类
|
|
395
|
-
if plugin_config:
|
|
396
|
-
metadata.config = plugin_config
|
|
456
|
+
if plugin_config and metadata.star_cls_type:
|
|
397
457
|
try:
|
|
398
458
|
metadata.star_cls = metadata.star_cls_type(
|
|
399
|
-
context=self.context,
|
|
459
|
+
context=self.context,
|
|
460
|
+
config=plugin_config,
|
|
400
461
|
)
|
|
401
462
|
except TypeError as _:
|
|
402
463
|
metadata.star_cls = metadata.star_cls_type(
|
|
403
|
-
context=self.context
|
|
464
|
+
context=self.context,
|
|
404
465
|
)
|
|
405
|
-
|
|
466
|
+
elif metadata.star_cls_type:
|
|
406
467
|
metadata.star_cls = metadata.star_cls_type(
|
|
407
|
-
context=self.context
|
|
468
|
+
context=self.context,
|
|
408
469
|
)
|
|
409
470
|
else:
|
|
410
471
|
logger.info(f"插件 {metadata.name} 已被禁用。")
|
|
@@ -413,39 +474,50 @@ class PluginManager:
|
|
|
413
474
|
metadata.root_dir_name = root_dir_name
|
|
414
475
|
metadata.reserved = reserved
|
|
415
476
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
"plugin_enable", {}
|
|
477
|
+
assert metadata.module_path is not None, (
|
|
478
|
+
f"插件 {metadata.name} 的模块路径为空。"
|
|
419
479
|
)
|
|
420
|
-
metadata.update_platform_compatibility(plugin_enable_config)
|
|
421
480
|
|
|
422
481
|
# 绑定 handler
|
|
423
482
|
related_handlers = (
|
|
424
483
|
star_handlers_registry.get_handlers_by_module_name(
|
|
425
|
-
metadata.module_path
|
|
484
|
+
metadata.module_path,
|
|
426
485
|
)
|
|
427
486
|
)
|
|
428
487
|
for handler in related_handlers:
|
|
429
488
|
handler.handler = functools.partial(
|
|
430
|
-
handler.handler,
|
|
489
|
+
handler.handler,
|
|
490
|
+
metadata.star_cls, # type: ignore
|
|
431
491
|
)
|
|
432
492
|
# 绑定 llm_tool handler
|
|
433
493
|
for func_tool in llm_tools.func_list:
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
494
|
+
if isinstance(func_tool, HandoffTool):
|
|
495
|
+
need_apply = []
|
|
496
|
+
sub_tools = func_tool.agent.tools
|
|
497
|
+
if sub_tools:
|
|
498
|
+
for sub_tool in sub_tools:
|
|
499
|
+
if isinstance(sub_tool, FunctionTool):
|
|
500
|
+
need_apply.append(sub_tool)
|
|
501
|
+
else:
|
|
502
|
+
need_apply = [func_tool]
|
|
503
|
+
|
|
504
|
+
for ft in need_apply:
|
|
505
|
+
if (
|
|
506
|
+
ft.handler
|
|
507
|
+
and ft.handler.__module__ == metadata.module_path
|
|
508
|
+
):
|
|
509
|
+
ft.handler_module_path = metadata.module_path
|
|
510
|
+
ft.handler = functools.partial(
|
|
511
|
+
ft.handler,
|
|
512
|
+
metadata.star_cls, # type: ignore
|
|
513
|
+
)
|
|
514
|
+
if ft.name in inactivated_llm_tools:
|
|
515
|
+
ft.active = False
|
|
444
516
|
|
|
445
517
|
else:
|
|
446
518
|
# v3.4.0 以前的方式注册插件
|
|
447
519
|
logger.debug(
|
|
448
|
-
f"插件 {path} 未通过装饰器注册。尝试通过旧版本方式载入。"
|
|
520
|
+
f"插件 {path} 未通过装饰器注册。尝试通过旧版本方式载入。",
|
|
449
521
|
)
|
|
450
522
|
classes = self._get_classes(module)
|
|
451
523
|
|
|
@@ -454,23 +526,24 @@ class PluginManager:
|
|
|
454
526
|
if plugin_config:
|
|
455
527
|
try:
|
|
456
528
|
obj = getattr(module, classes[0])(
|
|
457
|
-
context=self.context,
|
|
529
|
+
context=self.context,
|
|
530
|
+
config=plugin_config,
|
|
458
531
|
) # 实例化插件类
|
|
459
532
|
except TypeError as _:
|
|
460
533
|
obj = getattr(module, classes[0])(
|
|
461
|
-
context=self.context
|
|
534
|
+
context=self.context,
|
|
462
535
|
) # 实例化插件类
|
|
463
536
|
else:
|
|
464
537
|
obj = getattr(module, classes[0])(
|
|
465
|
-
context=self.context
|
|
538
|
+
context=self.context,
|
|
466
539
|
) # 实例化插件类
|
|
467
|
-
else:
|
|
468
|
-
logger.info(f"插件 {metadata.name} 已被禁用。")
|
|
469
540
|
|
|
470
|
-
metadata = None
|
|
471
541
|
metadata = self._load_plugin_metadata(
|
|
472
|
-
plugin_path=plugin_dir_path,
|
|
542
|
+
plugin_path=plugin_dir_path,
|
|
543
|
+
plugin_obj=obj,
|
|
473
544
|
)
|
|
545
|
+
if not metadata:
|
|
546
|
+
raise Exception(f"无法找到插件 {plugin_dir_path} 的元数据。")
|
|
474
547
|
metadata.star_cls = obj
|
|
475
548
|
metadata.config = plugin_config
|
|
476
549
|
metadata.module = module
|
|
@@ -485,9 +558,15 @@ class PluginManager:
|
|
|
485
558
|
if metadata.module_path in inactivated_plugins:
|
|
486
559
|
metadata.activated = False
|
|
487
560
|
|
|
561
|
+
# Plugin logo path
|
|
562
|
+
if os.path.exists(logo_path):
|
|
563
|
+
metadata.logo_path = logo_path
|
|
564
|
+
|
|
565
|
+
assert metadata.module_path, f"插件 {metadata.name} 模块路径为空"
|
|
566
|
+
|
|
488
567
|
full_names = []
|
|
489
568
|
for handler in star_handlers_registry.get_handlers_by_module_name(
|
|
490
|
-
metadata.module_path
|
|
569
|
+
metadata.module_path,
|
|
491
570
|
):
|
|
492
571
|
full_names.append(handler.handler_full_name)
|
|
493
572
|
|
|
@@ -497,7 +576,8 @@ class PluginManager:
|
|
|
497
576
|
and handler.handler_name in alter_cmd[metadata.name]
|
|
498
577
|
):
|
|
499
578
|
cmd_type = alter_cmd[metadata.name][handler.handler_name].get(
|
|
500
|
-
"permission",
|
|
579
|
+
"permission",
|
|
580
|
+
"member",
|
|
501
581
|
)
|
|
502
582
|
found_permission_filter = False
|
|
503
583
|
for filter_ in handler.event_filters:
|
|
@@ -513,18 +593,18 @@ class PluginManager:
|
|
|
513
593
|
PermissionTypeFilter(
|
|
514
594
|
PermissionType.ADMIN
|
|
515
595
|
if cmd_type == "admin"
|
|
516
|
-
else PermissionType.MEMBER
|
|
517
|
-
)
|
|
596
|
+
else PermissionType.MEMBER,
|
|
597
|
+
),
|
|
518
598
|
)
|
|
519
599
|
|
|
520
600
|
logger.debug(
|
|
521
|
-
f"插入权限过滤器 {cmd_type} 到 {metadata.name} 的 {handler.handler_name} 方法。"
|
|
601
|
+
f"插入权限过滤器 {cmd_type} 到 {metadata.name} 的 {handler.handler_name} 方法。",
|
|
522
602
|
)
|
|
523
603
|
|
|
524
604
|
metadata.star_handler_full_names = full_names
|
|
525
605
|
|
|
526
606
|
# 执行 initialize() 方法
|
|
527
|
-
if hasattr(metadata.star_cls, "initialize"):
|
|
607
|
+
if hasattr(metadata.star_cls, "initialize") and metadata.star_cls:
|
|
528
608
|
await metadata.star_cls.initialize()
|
|
529
609
|
|
|
530
610
|
except BaseException as e:
|
|
@@ -533,7 +613,7 @@ class PluginManager:
|
|
|
533
613
|
for line in errors.split("\n"):
|
|
534
614
|
logger.error(f"| {line}")
|
|
535
615
|
logger.error("----------------------------------")
|
|
536
|
-
fail_rec += f"加载 {root_dir_name} 插件时出现问题,原因 {
|
|
616
|
+
fail_rec += f"加载 {root_dir_name} 插件时出现问题,原因 {e!s}。\n"
|
|
537
617
|
|
|
538
618
|
# 清除 pip.main 导致的多余的 logging handlers
|
|
539
619
|
for handler in logging.root.handlers[:]:
|
|
@@ -541,9 +621,8 @@ class PluginManager:
|
|
|
541
621
|
|
|
542
622
|
if not fail_rec:
|
|
543
623
|
return True, None
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
return False, fail_rec
|
|
624
|
+
self.failed_plugin_info = fail_rec
|
|
625
|
+
return False, fail_rec
|
|
547
626
|
|
|
548
627
|
async def install_plugin(self, repo_url: str, proxy=""):
|
|
549
628
|
"""从仓库 URL 安装插件
|
|
@@ -559,75 +638,141 @@ class PluginManager:
|
|
|
559
638
|
- repo: 插件的仓库 URL
|
|
560
639
|
- readme: README.md 文件的内容(如果存在)
|
|
561
640
|
如果找不到插件元数据则返回 None。
|
|
562
|
-
"""
|
|
563
|
-
plugin_path = await self.updator.install(repo_url, proxy)
|
|
564
|
-
# reload the plugin
|
|
565
|
-
dir_name = os.path.basename(plugin_path)
|
|
566
|
-
await self.load(specified_dir_name=dir_name)
|
|
567
|
-
|
|
568
|
-
# Get the plugin metadata to return repo info
|
|
569
|
-
plugin = self.context.get_registered_star(dir_name)
|
|
570
|
-
if not plugin:
|
|
571
|
-
# Try to find by other name if directory name doesn't match plugin name
|
|
572
|
-
for star in self.context.get_all_stars():
|
|
573
|
-
if star.root_dir_name == dir_name:
|
|
574
|
-
plugin = star
|
|
575
|
-
break
|
|
576
|
-
|
|
577
|
-
# Extract README.md content if exists
|
|
578
|
-
readme_content = None
|
|
579
|
-
readme_path = os.path.join(plugin_path, "README.md")
|
|
580
|
-
if not os.path.exists(readme_path):
|
|
581
|
-
readme_path = os.path.join(plugin_path, "readme.md")
|
|
582
641
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
642
|
+
"""
|
|
643
|
+
async with self._pm_lock:
|
|
644
|
+
plugin_path = await self.updator.install(repo_url, proxy)
|
|
645
|
+
# reload the plugin
|
|
646
|
+
dir_name = os.path.basename(plugin_path)
|
|
647
|
+
await self.load(specified_dir_name=dir_name)
|
|
648
|
+
|
|
649
|
+
# Get the plugin metadata to return repo info
|
|
650
|
+
plugin = self.context.get_registered_star(dir_name)
|
|
651
|
+
if not plugin:
|
|
652
|
+
# Try to find by other name if directory name doesn't match plugin name
|
|
653
|
+
for star in self.context.get_all_stars():
|
|
654
|
+
if star.root_dir_name == dir_name:
|
|
655
|
+
plugin = star
|
|
656
|
+
break
|
|
657
|
+
|
|
658
|
+
# Extract README.md content if exists
|
|
659
|
+
readme_content = None
|
|
660
|
+
readme_path = os.path.join(plugin_path, "README.md")
|
|
661
|
+
if not os.path.exists(readme_path):
|
|
662
|
+
readme_path = os.path.join(plugin_path, "readme.md")
|
|
663
|
+
|
|
664
|
+
if os.path.exists(readme_path):
|
|
665
|
+
try:
|
|
666
|
+
with open(readme_path, encoding="utf-8") as f:
|
|
667
|
+
readme_content = f.read()
|
|
668
|
+
except Exception as e:
|
|
669
|
+
logger.warning(
|
|
670
|
+
f"读取插件 {dir_name} 的 README.md 文件失败: {e!s}",
|
|
671
|
+
)
|
|
589
672
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
673
|
+
plugin_info = None
|
|
674
|
+
if plugin:
|
|
675
|
+
plugin_info = {
|
|
676
|
+
"repo": plugin.repo,
|
|
677
|
+
"readme": readme_content,
|
|
678
|
+
"name": plugin.name,
|
|
679
|
+
}
|
|
593
680
|
|
|
594
|
-
|
|
681
|
+
return plugin_info
|
|
595
682
|
|
|
596
|
-
async def uninstall_plugin(
|
|
683
|
+
async def uninstall_plugin(
|
|
684
|
+
self,
|
|
685
|
+
plugin_name: str,
|
|
686
|
+
delete_config: bool = False,
|
|
687
|
+
delete_data: bool = False,
|
|
688
|
+
):
|
|
597
689
|
"""卸载指定的插件。
|
|
598
690
|
|
|
599
691
|
Args:
|
|
600
692
|
plugin_name (str): 要卸载的插件名称
|
|
693
|
+
delete_config (bool): 是否删除插件配置文件,默认为 False
|
|
694
|
+
delete_data (bool): 是否删除插件数据,默认为 False
|
|
601
695
|
|
|
602
696
|
Raises:
|
|
603
697
|
Exception: 当插件不存在、是保留插件时,或删除插件文件夹失败时抛出异常
|
|
698
|
+
|
|
604
699
|
"""
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
700
|
+
async with self._pm_lock:
|
|
701
|
+
plugin = self.context.get_registered_star(plugin_name)
|
|
702
|
+
if not plugin:
|
|
703
|
+
raise Exception("插件不存在。")
|
|
704
|
+
if plugin.reserved:
|
|
705
|
+
raise Exception("该插件是 AstrBot 保留插件,无法卸载。")
|
|
706
|
+
root_dir_name = plugin.root_dir_name
|
|
707
|
+
ppath = self.plugin_store_path
|
|
708
|
+
|
|
709
|
+
# 终止插件
|
|
710
|
+
try:
|
|
711
|
+
await self._terminate_plugin(plugin)
|
|
712
|
+
except Exception as e:
|
|
713
|
+
logger.warning(traceback.format_exc())
|
|
714
|
+
logger.warning(
|
|
715
|
+
f"插件 {plugin_name} 未被正常终止 {e!s}, 可能会导致资源泄露等问题。",
|
|
716
|
+
)
|
|
612
717
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
except Exception as e:
|
|
617
|
-
logger.warning(traceback.format_exc())
|
|
618
|
-
logger.warning(
|
|
619
|
-
f"插件 {plugin_name} 未被正常终止 {str(e)}, 可能会导致资源泄露等问题。"
|
|
620
|
-
)
|
|
718
|
+
# 从 star_registry 和 star_map 中删除
|
|
719
|
+
if plugin.module_path is None or root_dir_name is None:
|
|
720
|
+
raise Exception(f"插件 {plugin_name} 数据不完整,无法卸载。")
|
|
621
721
|
|
|
622
|
-
|
|
623
|
-
await self._unbind_plugin(plugin_name, plugin.module_path)
|
|
722
|
+
await self._unbind_plugin(plugin_name, plugin.module_path)
|
|
624
723
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
724
|
+
# 删除插件文件夹
|
|
725
|
+
try:
|
|
726
|
+
remove_dir(os.path.join(ppath, root_dir_name))
|
|
727
|
+
except Exception as e:
|
|
728
|
+
raise Exception(
|
|
729
|
+
f"移除插件成功,但是删除插件文件夹失败: {e!s}。您可以手动删除该文件夹,位于 addons/plugins/ 下。",
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
# 删除插件配置文件
|
|
733
|
+
if delete_config and root_dir_name:
|
|
734
|
+
config_file = os.path.join(
|
|
735
|
+
self.plugin_config_path,
|
|
736
|
+
f"{root_dir_name}_config.json",
|
|
737
|
+
)
|
|
738
|
+
if os.path.exists(config_file):
|
|
739
|
+
try:
|
|
740
|
+
os.remove(config_file)
|
|
741
|
+
logger.info(f"已删除插件 {plugin_name} 的配置文件")
|
|
742
|
+
except Exception as e:
|
|
743
|
+
logger.warning(f"删除插件配置文件失败: {e!s}")
|
|
744
|
+
|
|
745
|
+
# 删除插件持久化数据
|
|
746
|
+
# 注意:需要检查两个可能的目录名(plugin_data 和 plugins_data)
|
|
747
|
+
# data/temp 目录可能被多个插件共享,不自动删除以防误删
|
|
748
|
+
if delete_data and root_dir_name:
|
|
749
|
+
data_base_dir = os.path.dirname(ppath) # data/
|
|
750
|
+
|
|
751
|
+
# 删除 data/plugin_data 下的插件持久化数据(单数形式,新版本)
|
|
752
|
+
plugin_data_dir = os.path.join(
|
|
753
|
+
data_base_dir, "plugin_data", root_dir_name
|
|
754
|
+
)
|
|
755
|
+
if os.path.exists(plugin_data_dir):
|
|
756
|
+
try:
|
|
757
|
+
remove_dir(plugin_data_dir)
|
|
758
|
+
logger.info(
|
|
759
|
+
f"已删除插件 {plugin_name} 的持久化数据 (plugin_data)"
|
|
760
|
+
)
|
|
761
|
+
except Exception as e:
|
|
762
|
+
logger.warning(f"删除插件持久化数据失败 (plugin_data): {e!s}")
|
|
763
|
+
|
|
764
|
+
# 删除 data/plugins_data 下的插件持久化数据(复数形式,旧版本兼容)
|
|
765
|
+
plugins_data_dir = os.path.join(
|
|
766
|
+
data_base_dir, "plugins_data", root_dir_name
|
|
767
|
+
)
|
|
768
|
+
if os.path.exists(plugins_data_dir):
|
|
769
|
+
try:
|
|
770
|
+
remove_dir(plugins_data_dir)
|
|
771
|
+
logger.info(
|
|
772
|
+
f"已删除插件 {plugin_name} 的持久化数据 (plugins_data)"
|
|
773
|
+
)
|
|
774
|
+
except Exception as e:
|
|
775
|
+
logger.warning(f"删除插件持久化数据失败 (plugins_data): {e!s}")
|
|
631
776
|
|
|
632
777
|
async def _unbind_plugin(self, plugin_name: str, plugin_module_path: str):
|
|
633
778
|
"""解绑并移除一个插件。
|
|
@@ -635,6 +780,7 @@ class PluginManager:
|
|
|
635
780
|
Args:
|
|
636
781
|
plugin_name: 要解绑的插件名称
|
|
637
782
|
plugin_module_path: 插件的完整模块路径
|
|
783
|
+
|
|
638
784
|
"""
|
|
639
785
|
plugin = None
|
|
640
786
|
del star_map[plugin_module_path]
|
|
@@ -644,10 +790,10 @@ class PluginManager:
|
|
|
644
790
|
del star_registry[i]
|
|
645
791
|
break
|
|
646
792
|
for handler in star_handlers_registry.get_handlers_by_module_name(
|
|
647
|
-
plugin_module_path
|
|
793
|
+
plugin_module_path,
|
|
648
794
|
):
|
|
649
795
|
logger.info(
|
|
650
|
-
f"移除了插件 {plugin_name} 的处理函数 {handler.handler_name} ({len(star_handlers_registry)})"
|
|
796
|
+
f"移除了插件 {plugin_name} 的处理函数 {handler.handler_name} ({len(star_handlers_registry)})",
|
|
651
797
|
)
|
|
652
798
|
star_handlers_registry.remove(handler)
|
|
653
799
|
|
|
@@ -658,8 +804,25 @@ class PluginManager:
|
|
|
658
804
|
]:
|
|
659
805
|
del star_handlers_registry.star_handlers_map[k]
|
|
660
806
|
|
|
807
|
+
# llm_tools 中移除该插件的工具函数绑定
|
|
808
|
+
to_remove = []
|
|
809
|
+
for func_tool in llm_tools.func_list:
|
|
810
|
+
mp = func_tool.handler_module_path
|
|
811
|
+
if (
|
|
812
|
+
mp
|
|
813
|
+
and mp.startswith(plugin_module_path)
|
|
814
|
+
and not mp.endswith(("packages", "data.plugins"))
|
|
815
|
+
):
|
|
816
|
+
to_remove.append(func_tool)
|
|
817
|
+
for func_tool in to_remove:
|
|
818
|
+
llm_tools.func_list.remove(func_tool)
|
|
819
|
+
|
|
820
|
+
if plugin is None:
|
|
821
|
+
return
|
|
822
|
+
|
|
661
823
|
self._purge_modules(
|
|
662
|
-
root_dir_name=plugin.root_dir_name,
|
|
824
|
+
root_dir_name=plugin.root_dir_name,
|
|
825
|
+
is_reserved=plugin.reserved,
|
|
663
826
|
)
|
|
664
827
|
|
|
665
828
|
async def update_plugin(self, plugin_name: str, proxy=""):
|
|
@@ -674,41 +837,48 @@ class PluginManager:
|
|
|
674
837
|
await self.reload(plugin_name)
|
|
675
838
|
|
|
676
839
|
async def turn_off_plugin(self, plugin_name: str):
|
|
677
|
-
"""
|
|
678
|
-
禁用一个插件。
|
|
840
|
+
"""禁用一个插件。
|
|
679
841
|
调用插件的 terminate() 方法,
|
|
680
842
|
将插件的 module_path 加入到 data/shared_preferences.json 的 inactivated_plugins 列表中。
|
|
681
843
|
并且同时将插件启用的 llm_tool 禁用。
|
|
682
844
|
"""
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
# 调用插件的终止方法
|
|
688
|
-
await self._terminate_plugin(plugin)
|
|
689
|
-
|
|
690
|
-
# 加入到 shared_preferences 中
|
|
691
|
-
inactivated_plugins: list = sp.get("inactivated_plugins", [])
|
|
692
|
-
if plugin.module_path not in inactivated_plugins:
|
|
693
|
-
inactivated_plugins.append(plugin.module_path)
|
|
845
|
+
async with self._pm_lock:
|
|
846
|
+
plugin = self.context.get_registered_star(plugin_name)
|
|
847
|
+
if not plugin:
|
|
848
|
+
raise Exception("插件不存在。")
|
|
694
849
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
) # 后向兼容
|
|
850
|
+
# 调用插件的终止方法
|
|
851
|
+
await self._terminate_plugin(plugin)
|
|
698
852
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
853
|
+
# 加入到 shared_preferences 中
|
|
854
|
+
inactivated_plugins: list = await sp.global_get("inactivated_plugins", [])
|
|
855
|
+
if plugin.module_path not in inactivated_plugins:
|
|
856
|
+
inactivated_plugins.append(plugin.module_path)
|
|
857
|
+
|
|
858
|
+
inactivated_llm_tools: list = list(
|
|
859
|
+
set(await sp.global_get("inactivated_llm_tools", [])),
|
|
860
|
+
) # 后向兼容
|
|
861
|
+
|
|
862
|
+
# 禁用插件启用的 llm_tool
|
|
863
|
+
for func_tool in llm_tools.func_list:
|
|
864
|
+
mp = func_tool.handler_module_path
|
|
865
|
+
if (
|
|
866
|
+
plugin.module_path
|
|
867
|
+
and mp
|
|
868
|
+
and plugin.module_path.startswith(mp)
|
|
869
|
+
and not mp.endswith(("packages", "data.plugins"))
|
|
870
|
+
):
|
|
871
|
+
func_tool.active = False
|
|
872
|
+
if func_tool.name not in inactivated_llm_tools:
|
|
873
|
+
inactivated_llm_tools.append(func_tool.name)
|
|
705
874
|
|
|
706
|
-
|
|
707
|
-
|
|
875
|
+
await sp.global_put("inactivated_plugins", inactivated_plugins)
|
|
876
|
+
await sp.global_put("inactivated_llm_tools", inactivated_llm_tools)
|
|
708
877
|
|
|
709
|
-
|
|
878
|
+
plugin.activated = False
|
|
710
879
|
|
|
711
|
-
|
|
880
|
+
@staticmethod
|
|
881
|
+
async def _terminate_plugin(star_metadata: StarMetadata):
|
|
712
882
|
"""终止插件,调用插件的 terminate() 和 __del__() 方法"""
|
|
713
883
|
logger.info(f"正在终止插件 {star_metadata.name} ...")
|
|
714
884
|
|
|
@@ -717,35 +887,43 @@ class PluginManager:
|
|
|
717
887
|
logger.debug(f"插件 {star_metadata.name} 未被激活,不需要终止,跳过。")
|
|
718
888
|
return
|
|
719
889
|
|
|
720
|
-
if
|
|
890
|
+
if star_metadata.star_cls is None:
|
|
891
|
+
return
|
|
892
|
+
|
|
893
|
+
if "__del__" in star_metadata.star_cls_type.__dict__:
|
|
721
894
|
asyncio.get_event_loop().run_in_executor(
|
|
722
|
-
None,
|
|
895
|
+
None,
|
|
896
|
+
star_metadata.star_cls.__del__,
|
|
723
897
|
)
|
|
724
|
-
elif
|
|
898
|
+
elif "terminate" in star_metadata.star_cls_type.__dict__:
|
|
725
899
|
await star_metadata.star_cls.terminate()
|
|
726
900
|
|
|
727
901
|
async def turn_on_plugin(self, plugin_name: str):
|
|
728
902
|
plugin = self.context.get_registered_star(plugin_name)
|
|
729
|
-
|
|
730
|
-
|
|
903
|
+
if plugin is None:
|
|
904
|
+
raise Exception(f"插件 {plugin_name} 不存在。")
|
|
905
|
+
inactivated_plugins: list = await sp.global_get("inactivated_plugins", [])
|
|
906
|
+
inactivated_llm_tools: list = await sp.global_get("inactivated_llm_tools", [])
|
|
731
907
|
if plugin.module_path in inactivated_plugins:
|
|
732
908
|
inactivated_plugins.remove(plugin.module_path)
|
|
733
|
-
sp.
|
|
909
|
+
await sp.global_put("inactivated_plugins", inactivated_plugins)
|
|
734
910
|
|
|
735
911
|
# 启用插件启用的 llm_tool
|
|
736
912
|
for func_tool in llm_tools.func_list:
|
|
913
|
+
mp = func_tool.handler_module_path
|
|
737
914
|
if (
|
|
738
|
-
|
|
915
|
+
plugin.module_path
|
|
916
|
+
and mp
|
|
917
|
+
and plugin.module_path.startswith(mp)
|
|
918
|
+
and not mp.endswith(("packages", "data.plugins"))
|
|
739
919
|
and func_tool.name in inactivated_llm_tools
|
|
740
920
|
):
|
|
741
921
|
inactivated_llm_tools.remove(func_tool.name)
|
|
742
922
|
func_tool.active = True
|
|
743
|
-
sp.
|
|
923
|
+
await sp.global_put("inactivated_llm_tools", inactivated_llm_tools)
|
|
744
924
|
|
|
745
925
|
await self.reload(plugin_name)
|
|
746
926
|
|
|
747
|
-
# plugin.activated = True
|
|
748
|
-
|
|
749
927
|
async def install_plugin_from_file(self, zip_file_path: str):
|
|
750
928
|
dir_name = os.path.basename(zip_file_path).replace(".zip", "")
|
|
751
929
|
dir_name = dir_name.removesuffix("-master").removesuffix("-main").lower()
|
|
@@ -756,7 +934,7 @@ class PluginManager:
|
|
|
756
934
|
try:
|
|
757
935
|
os.remove(zip_file_path)
|
|
758
936
|
except BaseException as e:
|
|
759
|
-
logger.warning(f"删除插件压缩包失败: {
|
|
937
|
+
logger.warning(f"删除插件压缩包失败: {e!s}")
|
|
760
938
|
# await self.reload()
|
|
761
939
|
await self.load(specified_dir_name=dir_name)
|
|
762
940
|
|
|
@@ -777,13 +955,17 @@ class PluginManager:
|
|
|
777
955
|
|
|
778
956
|
if os.path.exists(readme_path):
|
|
779
957
|
try:
|
|
780
|
-
with open(readme_path,
|
|
958
|
+
with open(readme_path, encoding="utf-8") as f:
|
|
781
959
|
readme_content = f.read()
|
|
782
960
|
except Exception as e:
|
|
783
|
-
logger.warning(f"读取插件 {dir_name} 的 README.md 文件失败: {
|
|
961
|
+
logger.warning(f"读取插件 {dir_name} 的 README.md 文件失败: {e!s}")
|
|
784
962
|
|
|
785
963
|
plugin_info = None
|
|
786
964
|
if plugin:
|
|
787
|
-
plugin_info = {
|
|
965
|
+
plugin_info = {
|
|
966
|
+
"repo": plugin.repo,
|
|
967
|
+
"readme": readme_content,
|
|
968
|
+
"name": plugin.name,
|
|
969
|
+
}
|
|
788
970
|
|
|
789
971
|
return plugin_info
|