From b62b1f38704ffdd6ee1f166dd00260e898656c6e Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 7 Apr 2025 20:27:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E8=83=BD=E9=92=88=E5=AF=B9=E4=B8=8D=E5=90=8C=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=B9=B3=E5=8F=B0=E5=BC=80=E5=90=AF=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed: chore: merge master branch chore: merge from master branch chore: rename updateAllPlatformCompatibility to update_all_platform_compatibility for consistency Reviewed by: @Raven95676 @Soulter --- astrbot/core/pipeline/__init__.py | 3 + .../pipeline/platform_compatibility/stage.py | 52 +++++ .../process_stage/method/llm_request.py | 15 +- .../process_stage/method/star_request.py | 11 + astrbot/core/pipeline/respond/stage.py | 2 +- .../core/pipeline/result_decorate/stage.py | 2 +- astrbot/core/platform/astr_message_event.py | 3 + astrbot/core/platform/platform_metadata.py | 2 + .../aiocqhttp/aiocqhttp_platform_adapter.py | 5 +- .../sources/dingtalk/dingtalk_adapter.py | 5 +- .../gewechat/gewechat_platform_adapter.py | 5 +- .../platform/sources/lark/lark_adapter.py | 5 +- .../qqofficial/qqofficial_platform_adapter.py | 5 +- .../qqofficial_webhook/qo_webhook_adapter.py | 5 +- .../platform/sources/telegram/tg_adapter.py | 3 +- .../sources/webchat/webchat_adapter.py | 3 +- astrbot/core/star/filter/command.py | 0 astrbot/core/star/filter/command_group.py | 0 astrbot/core/star/star.py | 24 ++ astrbot/core/star/star_handler.py | 72 ++++-- astrbot/core/star/star_manager.py | 32 ++- astrbot/dashboard/routes/plugin.py | 119 +++++++++- dashboard/src/views/ExtensionPage.vue | 216 +++++++++++++++++- 23 files changed, 542 insertions(+), 47 deletions(-) create mode 100644 astrbot/core/pipeline/platform_compatibility/stage.py mode change 100644 => 100755 astrbot/core/star/filter/command.py mode change 100644 => 100755 astrbot/core/star/filter/command_group.py diff --git a/astrbot/core/pipeline/__init__.py b/astrbot/core/pipeline/__init__.py index b97fc0f12..406fcc796 100644 --- a/astrbot/core/pipeline/__init__.py +++ b/astrbot/core/pipeline/__init__.py @@ -7,6 +7,7 @@ from .waking_check.stage import WakingCheckStage from .whitelist_check.stage import WhitelistCheckStage from .rate_limit_check.stage import RateLimitStage from .content_safety_check.stage import ContentSafetyCheckStage +from .platform_compatibility.stage import PlatformCompatibilityStage from .preprocess_stage.stage import PreProcessStage from .process_stage.stage import ProcessStage from .result_decorate.stage import ResultDecorateStage @@ -18,6 +19,7 @@ STAGES_ORDER = [ "WhitelistCheckStage", # 检查是否在群聊/私聊白名单 "RateLimitStage", # 检查会话是否超过频率限制 "ContentSafetyCheckStage", # 检查内容安全 + "PlatformCompatibilityStage", # 检查所有处理器的平台兼容性 "PreProcessStage", # 预处理 "ProcessStage", # 交由 Stars 处理(a.k.a 插件),或者 LLM 调用 "ResultDecorateStage", # 处理结果,比如添加回复前缀、t2i、转换为语音 等 @@ -29,6 +31,7 @@ __all__ = [ "WhitelistCheckStage", "RateLimitStage", "ContentSafetyCheckStage", + "PlatformCompatibilityStage", "PreProcessStage", "ProcessStage", "ResultDecorateStage", diff --git a/astrbot/core/pipeline/platform_compatibility/stage.py b/astrbot/core/pipeline/platform_compatibility/stage.py new file mode 100644 index 000000000..0b0b04fda --- /dev/null +++ b/astrbot/core/pipeline/platform_compatibility/stage.py @@ -0,0 +1,52 @@ +from ..stage import Stage, register_stage +from ..context import PipelineContext +from typing import Union, AsyncGenerator +from astrbot.core.platform.astr_message_event import AstrMessageEvent +from astrbot.core.star.star import star_map +from astrbot.core import logger + + +@register_stage +class PlatformCompatibilityStage(Stage): + """检查所有处理器的平台兼容性。 + + 这个阶段会检查所有处理器是否在当前平台启用,如果未启用则设置platform_compatible属性为False。 + """ + + async def initialize(self, ctx: PipelineContext) -> None: + """初始化平台兼容性检查阶段 + + Args: + ctx (PipelineContext): 消息管道上下文对象, 包括配置和插件管理器 + """ + self.ctx = ctx + + async def process( + self, event: AstrMessageEvent + ) -> Union[None, AsyncGenerator[None, None]]: + # 获取当前平台ID + platform_id = event.get_platform_id() + + # 获取已激活的处理器 + activated_handlers = event.get_extra("activated_handlers") + if activated_handlers is None: + activated_handlers = [] + + # 标记不兼容的处理器 + for handler in activated_handlers: + # 检查处理器是否在当前平台启用 + enabled = handler.is_enabled_for_platform(platform_id) + if not enabled: + if handler.handler_module_path in star_map: + plugin_name = star_map[handler.handler_module_path].name + logger.debug( + f"[PlatformCompatibilityStage] 插件 {plugin_name} 在平台 {platform_id} 未启用,标记处理器 {handler.handler_name} 为平台不兼容" + ) + # 设置处理器为平台不兼容状态 + handler.platform_compatible = False + else: + # 确保处理器为平台兼容状态 + handler.platform_compatible = True + + # 更新已激活的处理器列表 + event.set_extra("activated_handlers", activated_handlers) diff --git a/astrbot/core/pipeline/process_stage/method/llm_request.py b/astrbot/core/pipeline/process_stage/method/llm_request.py index c6a87b37c..3a129ea80 100644 --- a/astrbot/core/pipeline/process_stage/method/llm_request.py +++ b/astrbot/core/pipeline/process_stage/method/llm_request.py @@ -108,8 +108,10 @@ class LLMRequestSubStage(Stage): # 执行请求 LLM 前事件钩子。 # 装饰 system_prompt 等功能 + # 获取当前平台ID + platform_id = event.get_platform_id() handlers = star_handlers_registry.get_handlers_by_event_type( - EventType.OnLLMRequestEvent + EventType.OnLLMRequestEvent, platform_id=platform_id ) for handler in handlers: try: @@ -350,6 +352,8 @@ class LLMRequestSubStage(Stage): llm_response.tools_call_args, llm_response.tools_call_ids, ): + + try: func_tool = req.func_tool.get_func(func_tool_name) if func_tool.origin == "mcp": @@ -368,6 +372,15 @@ class LLMRequestSubStage(Stage): ) ) else: + # 获取处理器,过滤掉平台不兼容的处理器 + platform_id = event.get_platform_id() + if not func_tool.handler.is_enabled_for_platform(platform_id): + logger.debug( + f"处理器 {func_tool_name} 在当前平台不兼容,跳过执行" + ) + # 直接跳过,不添加任何消息到tool_call_result + continue + logger.info( f"调用工具函数:{func_tool_name},参数:{func_tool_args}" ) diff --git a/astrbot/core/pipeline/process_stage/method/star_request.py b/astrbot/core/pipeline/process_stage/method/star_request.py index d369e53ed..c7817e49c 100644 --- a/astrbot/core/pipeline/process_stage/method/star_request.py +++ b/astrbot/core/pipeline/process_stage/method/star_request.py @@ -31,7 +31,18 @@ class StarRequestSubStage(Stage): ) if not handlers_parsed_params: handlers_parsed_params = {} + for handler in activated_handlers: + # 检查处理器是否在当前平台兼容 + if ( + hasattr(handler, "platform_compatible") + and handler.platform_compatible is False + ): + logger.debug( + f"处理器 {handler.handler_name} 在当前平台不兼容,跳过执行" + ) + continue + params = handlers_parsed_params.get(handler.handler_full_name, {}) try: if handler.handler_module_path not in star_map: diff --git a/astrbot/core/pipeline/respond/stage.py b/astrbot/core/pipeline/respond/stage.py index 0d5044054..60a052454 100644 --- a/astrbot/core/pipeline/respond/stage.py +++ b/astrbot/core/pipeline/respond/stage.py @@ -198,7 +198,7 @@ class RespondStage(Stage): ) handlers = star_handlers_registry.get_handlers_by_event_type( - EventType.OnAfterMessageSentEvent + EventType.OnAfterMessageSentEvent, platform_id=event.get_platform_id() ) for handler in handlers: try: diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index a0be2423b..957e2a491 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -96,7 +96,7 @@ class ResultDecorateStage(Stage): # 发送消息前事件钩子 handlers = star_handlers_registry.get_handlers_by_event_type( - EventType.OnDecoratingResultEvent + EventType.OnDecoratingResultEvent, platform_id=event.get_platform_id() ) for handler in handlers: try: diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py index 8d3bc4c59..96a7ad6f1 100644 --- a/astrbot/core/platform/astr_message_event.py +++ b/astrbot/core/platform/astr_message_event.py @@ -81,6 +81,9 @@ class AstrMessageEvent(abc.ABC): def get_platform_name(self): return self.platform_meta.name + def get_platform_id(self): + return self.platform_meta.id + def get_message_str(self) -> str: """ 获取消息字符串。 diff --git a/astrbot/core/platform/platform_metadata.py b/astrbot/core/platform/platform_metadata.py index 48fe23af7..dd0e93fec 100644 --- a/astrbot/core/platform/platform_metadata.py +++ b/astrbot/core/platform/platform_metadata.py @@ -7,6 +7,8 @@ class PlatformMetadata: """平台的名称""" description: str """平台的描述""" + id: str = None + """平台的唯一标识符,用于配置中识别特定平台""" default_config_tmpl: dict = None """平台的默认配置模板""" diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index e41071a56..88f2ae3fc 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -39,8 +39,9 @@ class AiocqhttpAdapter(Platform): self.port = platform_config["ws_reverse_port"] self.metadata = PlatformMetadata( - "aiocqhttp", - "适用于 OneBot 标准的消息平台适配器,支持反向 WebSockets。", + name="aiocqhttp", + description="适用于 OneBot 标准的消息平台适配器,支持反向 WebSockets。", + id=self.config.get("id"), ) self.bot = CQHttp( diff --git a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py index 95347172b..7a83a8abe 100644 --- a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +++ b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py @@ -73,8 +73,9 @@ class DingtalkPlatformAdapter(Platform): def meta(self) -> PlatformMetadata: return PlatformMetadata( - "dingtalk", - "钉钉机器人官方 API 适配器", + name="dingtalk", + description="钉钉机器人官方 API 适配器", + id=self.config.get("id"), ) async def convert_msg( diff --git a/astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py b/astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py index acf39197f..930359837 100644 --- a/astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +++ b/astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py @@ -60,8 +60,9 @@ class GewechatPlatformAdapter(Platform): @override def meta(self) -> PlatformMetadata: return PlatformMetadata( - "gewechat", - "基于 gewechat 的 Wechat 适配器", + name="gewechat", + description="基于 gewechat 的 Wechat 适配器", + id=self.config.get("id"), ) async def terminate(self): diff --git a/astrbot/core/platform/sources/lark/lark_adapter.py b/astrbot/core/platform/sources/lark/lark_adapter.py index cbc3a45bb..8ea2ce36b 100644 --- a/astrbot/core/platform/sources/lark/lark_adapter.py +++ b/astrbot/core/platform/sources/lark/lark_adapter.py @@ -70,8 +70,9 @@ class LarkPlatformAdapter(Platform): def meta(self) -> PlatformMetadata: return PlatformMetadata( - "lark", - "飞书机器人官方 API 适配器", + name="lark", + description="飞书机器人官方 API 适配器", + id=self.config.get("id"), ) async def convert_msg(self, event: lark.im.v1.P2ImMessageReceiveV1): diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py index 57bc8683f..d5285f759 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py @@ -126,8 +126,9 @@ class QQOfficialPlatformAdapter(Platform): def meta(self) -> PlatformMetadata: return PlatformMetadata( - "qq_official", - "QQ 机器人官方 API 适配器", + name="qq_official", + description="QQ 机器人官方 API 适配器", + id=self.config.get("id"), ) @staticmethod diff --git a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py index ede09e7fd..cc12e9765 100644 --- a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +++ b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py @@ -99,8 +99,9 @@ class QQOfficialWebhookPlatformAdapter(Platform): def meta(self) -> PlatformMetadata: return PlatformMetadata( - "qq_official_webhook", - "QQ 机器人官方 API 适配器", + name="qq_official_webhook", + description="QQ 机器人官方 API 适配器", + id=self.config.get("id"), ) async def run(self): diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index 12f17a819..9ff761c06 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -80,8 +80,7 @@ class TelegramPlatformAdapter(Platform): @override def meta(self) -> PlatformMetadata: return PlatformMetadata( - "telegram", - "telegram 适配器", + name="telegram", description="telegram 适配器", id=self.config.get("id") ) @override diff --git a/astrbot/core/platform/sources/webchat/webchat_adapter.py b/astrbot/core/platform/sources/webchat/webchat_adapter.py index 6fa3d5c59..01a042fb8 100644 --- a/astrbot/core/platform/sources/webchat/webchat_adapter.py +++ b/astrbot/core/platform/sources/webchat/webchat_adapter.py @@ -43,8 +43,7 @@ class WebChatAdapter(Platform): self.imgs_dir = "data/webchat/imgs" self.metadata = PlatformMetadata( - "webchat", - "webchat", + name="webchat", description="webchat", id=self.config.get("id") ) async def send_by_session( diff --git a/astrbot/core/star/filter/command.py b/astrbot/core/star/filter/command.py old mode 100644 new mode 100755 diff --git a/astrbot/core/star/filter/command_group.py b/astrbot/core/star/filter/command_group.py old mode 100644 new mode 100755 diff --git a/astrbot/core/star/star.py b/astrbot/core/star/star.py index 521513449..10cf90c8b 100644 --- a/astrbot/core/star/star.py +++ b/astrbot/core/star/star.py @@ -47,5 +47,29 @@ class StarMetadata: star_handler_full_names: List[str] = field(default_factory=list) """注册的 Handler 的全名列表""" + supported_platforms: Dict[str, bool] = field(default_factory=dict) + """插件支持的平台ID字典,key为平台ID,value为是否支持""" + def __str__(self) -> str: return f"StarMetadata({self.name}, {self.desc}, {self.version}, {self.repo})" + + def update_platform_compatibility(self, plugin_enable_config: dict) -> None: + """更新插件支持的平台列表 + + Args: + plugin_enable_config: 平台插件启用配置,即platform_settings.plugin_enable配置项 + """ + if not plugin_enable_config: + return + + # 清空之前的配置 + self.supported_platforms.clear() + + # 遍历所有平台配置 + for platform_id, plugins in plugin_enable_config.items(): + # 检查该插件在当前平台的配置 + if self.name in plugins: + self.supported_platforms[platform_id] = plugins[self.name] + else: + # 如果没有明确配置,默认为启用 + self.supported_platforms[platform_id] = True diff --git a/astrbot/core/star/star_handler.py b/astrbot/core/star/star_handler.py index 7be0e053c..0764f15f6 100644 --- a/astrbot/core/star/star_handler.py +++ b/astrbot/core/star/star_handler.py @@ -30,21 +30,36 @@ class StarHandlerRegistry(Generic[T]): print(handler.handler_full_name) def get_handlers_by_event_type( - self, event_type: EventType, only_activated=True + self, event_type: EventType, only_activated=True, platform_id=None ) -> List[StarHandlerMetadata]: - """通过事件类型获取 Handler""" - handlers = [ - handler - for _, handler in self._handlers - if handler.event_type == event_type - and ( - not only_activated - or ( - star_map[handler.handler_module_path] - and star_map[handler.handler_module_path].activated - ) - ) - ] + """通过事件类型获取 Handler + + Args: + event_type: 事件类型 + only_activated: 是否只返回已激活的插件的处理器 + platform_id: 平台ID,如果提供此参数,将过滤掉在此平台不兼容的处理器 + + Returns: + List[StarHandlerMetadata]: 处理器列表 + """ + handlers = [] + for _, handler in self._handlers: + if handler.event_type != event_type: + continue + + # 只激活的插件处理器 + if only_activated: + plugin = star_map.get(handler.handler_module_path) + if not (plugin and plugin.activated): + continue + + # 平台兼容性过滤 + if platform_id and event_type != EventType.OnAstrBotLoadedEvent: + if not handler.is_enabled_for_platform(platform_id): + continue + + handlers.append(handler) + return handlers def get_handler_by_full_name(self, full_name: str) -> StarHandlerMetadata: @@ -139,3 +154,32 @@ class StarHandlerMetadata: return self.extras_configs.get("priority", 0) < other.extras_configs.get( "priority", 0 ) + + def is_enabled_for_platform(self, platform_id: str) -> bool: + """检查插件是否在指定平台启用 + + Args: + platform_id: 平台ID,这是从event.get_platform_id()获取的,用于唯一标识平台实例 + + Returns: + bool: 是否启用,True表示启用,False表示禁用 + """ + plugin = star_map.get(self.handler_module_path) + + # 如果插件元数据不存在,默认允许执行 + if not plugin or not plugin.name: + return True + + # 先检查插件是否被激活 + if not plugin.activated: + return False + + # 直接使用StarMetadata中缓存的supported_platforms判断平台兼容性 + if ( + hasattr(plugin, "supported_platforms") + and platform_id in plugin.supported_platforms + ): + return plugin.supported_platforms[platform_id] + + # 如果没有缓存数据,默认允许执行 + return True diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 2d610f15c..a4ae48250 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -209,7 +209,31 @@ class PluginManager: await self._unbind_plugin(smd.name, specified_module_path) - return await self.load(specified_module_path) + result = await self.load(specified_module_path) + + # 更新所有插件的平台兼容性 + await self.update_all_platform_compatibility() + + return result + + async def update_all_platform_compatibility(self): + """更新所有插件的平台兼容性设置""" + # 获取最新的平台插件启用配置 + plugin_enable_config = self.config.get("platform_settings", {}).get( + "plugin_enable", {} + ) + logger.debug( + f"更新所有插件的平台兼容性设置,平台数量: {len(plugin_enable_config)}" + ) + + # 遍历所有插件,更新平台兼容性 + for plugin in self.context.get_all_stars(): + plugin.update_platform_compatibility(plugin_enable_config) + logger.debug( + f"插件 {plugin.name} 支持的平台: {list(plugin.supported_platforms.keys())}" + ) + + return True async def load(self, specified_module_path=None, specified_dir_name=None): """载入插件。 @@ -320,6 +344,12 @@ class PluginManager: metadata.root_dir_name = root_dir_name metadata.reserved = reserved + # 更新插件的平台兼容性 + plugin_enable_config = self.config.get("platform_settings", {}).get( + "plugin_enable", {} + ) + metadata.update_platform_compatibility(plugin_enable_config) + # 绑定 handler related_handlers = ( star_handlers_registry.get_handlers_by_module_name( diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index 81d9b0bfe..9fb9d231a 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -38,6 +38,8 @@ class PluginRoute(Route): "/plugin/on": ("POST", self.on_plugin), "/plugin/reload": ("POST", self.reload_plugins), "/plugin/readme": ("GET", self.get_plugin_readme), + "/plugin/platform_enable/get": ("GET", self.get_plugin_platform_enable), + "/plugin/platform_enable/set": ("POST", self.set_plugin_platform_enable), } self.core_lifecycle = core_lifecycle self.plugin_manager = plugin_manager @@ -323,38 +325,131 @@ class PluginRoute(Route): async def get_plugin_readme(self): plugin_name = request.args.get("name") logger.debug(f"正在获取插件 {plugin_name} 的README文件内容") - + if not plugin_name: logger.warning("插件名称为空") return Response().error("插件名称不能为空").__dict__ - + plugin_obj = None for plugin in self.plugin_manager.context.get_all_stars(): if plugin.name == plugin_name: plugin_obj = plugin break - + if not plugin_obj: logger.warning(f"插件 {plugin_name} 不存在") return Response().error(f"插件 {plugin_name} 不存在").__dict__ - - plugin_dir = os.path.join(self.plugin_manager.plugin_store_path, plugin_obj.root_dir_name) - + + plugin_dir = os.path.join( + self.plugin_manager.plugin_store_path, plugin_obj.root_dir_name + ) + if not os.path.isdir(plugin_dir): logger.warning(f"无法找到插件目录: {plugin_dir}") return Response().error(f"无法找到插件 {plugin_name} 的目录").__dict__ - + readme_path = os.path.join(plugin_dir, "README.md") - + if not os.path.isfile(readme_path): logger.warning(f"插件 {plugin_name} 没有README文件") return Response().error(f"插件 {plugin_name} 没有README文件").__dict__ - + try: - with open(readme_path, 'r', encoding='utf-8') as f: + with open(readme_path, "r", encoding="utf-8") as f: readme_content = f.read() - - return Response().ok({"content": readme_content}, "成功获取README内容").__dict__ + + return ( + Response() + .ok({"content": readme_content}, "成功获取README内容") + .__dict__ + ) except Exception as e: logger.error(f"/api/plugin/readme: {traceback.format_exc()}") return Response().error(f"读取README文件失败: {str(e)}").__dict__ + + async def get_plugin_platform_enable(self): + """获取插件在各平台的可用性配置""" + try: + platform_enable = self.core_lifecycle.astrbot_config.get( + "platform_settings", {} + ).get("plugin_enable", {}) + + # 获取所有可用平台 + platforms = [] + + for platform in self.core_lifecycle.astrbot_config.get("platform", []): + platform_type = platform.get("type", "") + platform_id = platform.get("id", "") + + platforms.append( + { + "name": platform_id, # 使用type作为name,这是系统内部使用的平台名称 + "id": platform_id, # 保留id字段以便前端可以显示 + "type": platform_type, + "display_name": f"{platform_type}({platform_id})", + } + ) + + adjusted_platform_enable = {} + for platform_id, plugins in platform_enable.items(): + adjusted_platform_enable[platform_id] = plugins + + # 获取所有插件,包括系统内部插件 + plugins = [] + for plugin in self.plugin_manager.context.get_all_stars(): + plugins.append( + { + "name": plugin.name, + "desc": plugin.desc, + "reserved": plugin.reserved, # 添加reserved标志 + } + ) + + logger.debug( + f"获取插件平台配置: 原始配置={platform_enable}, 调整后={adjusted_platform_enable}" + ) + + return ( + Response() + .ok( + { + "platforms": platforms, + "plugins": plugins, + "platform_enable": adjusted_platform_enable, + } + ) + .__dict__ + ) + except Exception as e: + logger.error(f"/api/plugin/platform_enable/get: {traceback.format_exc()}") + return Response().error(str(e)).__dict__ + + async def set_plugin_platform_enable(self): + """设置插件在各平台的可用性配置""" + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + + try: + data = await request.json + platform_enable = data.get("platform_enable", {}) + + # 更新配置 + config = self.core_lifecycle.astrbot_config + platform_settings = config.get("platform_settings", {}) + platform_settings["plugin_enable"] = platform_enable + config["platform_settings"] = platform_settings + config.save_config() + + # 更新插件的平台兼容性缓存 + await self.plugin_manager.update_all_platform_compatibility() + + logger.info(f"插件平台可用性配置已更新: {platform_enable}") + + return Response().ok(None, "插件平台可用性配置已更新").__dict__ + except Exception as e: + logger.error(f"/api/plugin/platform_enable/set: {traceback.format_exc()}") + return Response().error(str(e)).__dict__ diff --git a/dashboard/src/views/ExtensionPage.vue b/dashboard/src/views/ExtensionPage.vue index 33f78dd01..2f34deccf 100644 --- a/dashboard/src/views/ExtensionPage.vue +++ b/dashboard/src/views/ExtensionPage.vue @@ -41,6 +41,14 @@ const readmeDialog = reactive({ pluginName: '', repoUrl: null }); +// 平台插件配置 +const platformEnableDialog = ref(false); +const platformEnableData = reactive({ + platforms: [], + plugins: [], + platform_enable: {} +}); +const loadingPlatformData = ref(false); const plugin_handler_info_headers = [ { title: '行为类型', key: 'event_type_h' }, @@ -238,6 +246,101 @@ const viewReadme = (plugin) => { readmeDialog.show = true; }; +// 获取插件平台可用性配置 +const getPlatformEnableConfig = async () => { + loadingPlatformData.value = true; + try { + const res = await axios.get('/api/plugin/platform_enable/get'); + if (res.data.status === "error") { + toast(res.data.message, "error"); + return; + } + + platformEnableData.platforms = res.data.data.platforms; + platformEnableData.plugins = res.data.data.plugins; + platformEnableData.platform_enable = res.data.data.platform_enable; + + // 如果没有平台,给出提示但仍显示对话框 + if (platformEnableData.platforms.length === 0) { + toast("未添加任何平台适配器,请先在平台管理中添加平台", "warning"); + } else { + // 确保每个平台都有一个配置对象 + platformEnableData.platforms.forEach(platform => { + if (!platformEnableData.platform_enable[platform.name]) { + platformEnableData.platform_enable[platform.name] = {}; + } + + // 确保每个插件在每个平台都有一个配置项 + platformEnableData.plugins.forEach(plugin => { + if (platformEnableData.platform_enable[platform.name][plugin.name] === undefined) { + platformEnableData.platform_enable[platform.name][plugin.name] = true; // 默认启用 + } + }); + }); + } + + platformEnableDialog.value = true; + } catch (err) { + toast("获取平台插件配置失败: " + err, "error"); + } finally { + loadingPlatformData.value = false; + } +}; + +// 保存插件平台可用性配置 +const savePlatformEnableConfig = async () => { + loadingPlatformData.value = true; + try { + const res = await axios.post('/api/plugin/platform_enable/set', { + platform_enable: platformEnableData.platform_enable + }); + + if (res.data.status === "error") { + toast(res.data.message, "error"); + return; + } + + toast(res.data.message, "success"); + platformEnableDialog.value = false; + } catch (err) { + toast("保存平台插件配置失败: " + err, "error"); + } finally { + loadingPlatformData.value = false; + } +}; + +// 全选指定平台的所有插件 +const selectAllPluginsForPlatform = (platformName, isSelected, onlyReserved = null) => { + // 确保平台存在于platform_enable中 + if (!platformEnableData.platform_enable[platformName]) { + platformEnableData.platform_enable[platformName] = {}; + } + + // 为所有插件设置相同的状态 + platformEnableData.plugins.forEach(plugin => { + // 如果onlyReserved为null,处理所有插件 + // 如果onlyReserved为true,只处理系统插件 + // 如果onlyReserved为false,只处理非系统插件 + if (onlyReserved === null || plugin.reserved === onlyReserved) { + platformEnableData.platform_enable[platformName][plugin.name] = isSelected; + } + }); +}; + +// 反选指定平台的所有插件 +const toggleAllPluginsForPlatform = (platformName) => { + // 确保平台存在于platform_enable中 + if (!platformEnableData.platform_enable[platformName]) { + platformEnableData.platform_enable[platformName] = {}; + } + + // 对每个插件进行反选操作 + platformEnableData.plugins.forEach(plugin => { + const currentState = platformEnableData.platform_enable[platformName][plugin.name]; + platformEnableData.platform_enable[platformName][plugin.name] = !currentState; + }); +}; + // 生命周期 onMounted(async () => { await getExtensions(); @@ -261,6 +364,9 @@ onMounted(async () => { {{ showReserved ? '隐藏系统保留插件' : '显示系统保留插件' }} + + 平台命令配置 + \ No newline at end of file + + + From e349671fdf16fabbbef7672ef007ffc28aa51dea Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 9 Apr 2025 18:45:40 +0800 Subject: [PATCH 2/3] format --- astrbot/core/pipeline/process_stage/method/llm_request.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/astrbot/core/pipeline/process_stage/method/llm_request.py b/astrbot/core/pipeline/process_stage/method/llm_request.py index 3a129ea80..a9f793626 100644 --- a/astrbot/core/pipeline/process_stage/method/llm_request.py +++ b/astrbot/core/pipeline/process_stage/method/llm_request.py @@ -352,8 +352,6 @@ class LLMRequestSubStage(Stage): llm_response.tools_call_args, llm_response.tools_call_ids, ): - - try: func_tool = req.func_tool.get_func(func_tool_name) if func_tool.origin == "mcp": From cd18806c393a0c6b40d3600688fca4c55401eab1 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Thu, 10 Apr 2025 11:01:04 +0800 Subject: [PATCH 3/3] perf: improve platform compatibility checks --- astrbot/core/config/default.py | 3 +++ astrbot/core/pipeline/platform_compatibility/stage.py | 4 ++++ astrbot/core/utils/shared_preferences.py | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 13be0b498..4a6364979 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -248,6 +248,9 @@ CONFIG_METADATA_2 = { "description": "平台设置", "type": "object", "items": { + "plugin_enable": { + "invisible": True, # 隐藏插件启用配置 + }, "unique_session": { "description": "会话隔离", "type": "bool", diff --git a/astrbot/core/pipeline/platform_compatibility/stage.py b/astrbot/core/pipeline/platform_compatibility/stage.py index 0b0b04fda..644912c26 100644 --- a/astrbot/core/pipeline/platform_compatibility/stage.py +++ b/astrbot/core/pipeline/platform_compatibility/stage.py @@ -3,6 +3,7 @@ from ..context import PipelineContext from typing import Union, AsyncGenerator from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.star.star import star_map +from astrbot.core.star.star_handler import StarHandlerMetadata from astrbot.core import logger @@ -34,6 +35,8 @@ class PlatformCompatibilityStage(Stage): # 标记不兼容的处理器 for handler in activated_handlers: + if not isinstance(handler, StarHandlerMetadata): + continue # 检查处理器是否在当前平台启用 enabled = handler.is_enabled_for_platform(platform_id) if not enabled: @@ -43,6 +46,7 @@ class PlatformCompatibilityStage(Stage): f"[PlatformCompatibilityStage] 插件 {plugin_name} 在平台 {platform_id} 未启用,标记处理器 {handler.handler_name} 为平台不兼容" ) # 设置处理器为平台不兼容状态 + # TODO: 更好的标记方式 handler.platform_compatible = False else: # 确保处理器为平台兼容状态 diff --git a/astrbot/core/utils/shared_preferences.py b/astrbot/core/utils/shared_preferences.py index bf88ba8db..b11987322 100644 --- a/astrbot/core/utils/shared_preferences.py +++ b/astrbot/core/utils/shared_preferences.py @@ -15,7 +15,7 @@ class SharedPreferences: def _save_preferences(self): with open(self.path, "w") as f: - json.dump(self._data, f, indent=4) + json.dump(self._data, f, indent=4, ensure_ascii=False) f.flush() def get(self, key, default=None):