diff --git a/zhenxun/builtin_plugins/hooks/auth/auth_admin.py b/zhenxun/builtin_plugins/hooks/auth/auth_admin.py index 4483303f..aed470e7 100644 --- a/zhenxun/builtin_plugins/hooks/auth/auth_admin.py +++ b/zhenxun/builtin_plugins/hooks/auth/auth_admin.py @@ -11,7 +11,6 @@ from zhenxun.utils.utils import get_entity_ids from .config import LOGGER_COMMAND, WARNING_THRESHOLD from .exception import SkipPluginException -from .utils import send_message async def auth_admin( @@ -51,29 +50,24 @@ async def auth_admin( user_level = max(user_level, group_users.user_level) if user_level < plugin.admin_level: - await send_message( - session, - [ + raise SkipPluginException( + f"{plugin.name}({plugin.module}) 管理员权限不足...", + tip_message=[ At(flag="user", target=session.user.id), f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}", ], - entity.user_id, - background=True, - ) - - raise SkipPluginException( - f"{plugin.name}({plugin.module}) 管理员权限不足..." + tip_check_tag=entity.user_id, + tip_background=True, ) elif global_user: if global_user.user_level < plugin.admin_level: - await send_message( - session, - f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}", - background=True, - ) - raise SkipPluginException( - f"{plugin.name}({plugin.module}) 管理员权限不足..." + f"{plugin.name}({plugin.module}) 管理员权限不足...", + tip_message=( + f"你的权限不足喔,该功能需要的权限等级: " + f"{plugin.admin_level}" + ), + tip_background=True, ) finally: # 记录执行时间 diff --git a/zhenxun/builtin_plugins/hooks/auth/auth_ban.py b/zhenxun/builtin_plugins/hooks/auth/auth_ban.py index 6aef96a2..6b1ee105 100644 --- a/zhenxun/builtin_plugins/hooks/auth/auth_ban.py +++ b/zhenxun/builtin_plugins/hooks/auth/auth_ban.py @@ -1,7 +1,6 @@ import asyncio import time -from nonebot.adapters import Bot from nonebot.matcher import Matcher from nonebot_plugin_alconna import At from nonebot_plugin_uninfo import Uninfo @@ -18,7 +17,7 @@ from zhenxun.utils.utils import EntityIDs, get_entity_ids from .config import LOGGER_COMMAND, WARNING_THRESHOLD from .exception import SkipPluginException -from .utils import freq, send_message +from .utils import freq Config.add_plugin_config( "hook", @@ -189,20 +188,15 @@ async def user_handle(plugin: PluginInfo, entity: EntityIDs, session: Uninfo) -> and ban_result and freq.is_send_limit_message(plugin, entity.user_id, False) ): - try: - await asyncio.wait_for( - send_message( - session, - [ - At(flag="user", target=entity.user_id), - f"{ban_result}\n在..在 {time_str} 后才会理你喔", - ], - entity.user_id, - ), - timeout=DB_TIMEOUT_SECONDS, - ) - except asyncio.TimeoutError: - logger.error(f"发送消息超时: {entity.user_id}", LOGGER_COMMAND) + raise SkipPluginException( + "用户处于黑名单中...", + tip_message=[ + At(flag="user", target=entity.user_id), + f"{ban_result}\n在..在 {time_str} 后才会理你喔", + ], + tip_check_tag=entity.user_id, + tip_timeout=DB_TIMEOUT_SECONDS, + ) raise SkipPluginException("用户处于黑名单中...") finally: # 记录执行时间 @@ -217,16 +211,16 @@ async def user_handle(plugin: PluginInfo, entity: EntityIDs, session: Uninfo) -> async def auth_ban( matcher: Matcher, - bot: Bot, session: Uninfo, plugin: PluginInfo, + *, entity: EntityIDs | None = None, + is_superuser: bool = False, ) -> None: """权限检查 - ban 检查 参数: matcher: Matcher - bot: Bot session: Uninfo """ start_time = time.time() @@ -237,7 +231,7 @@ async def auth_ban( return if entity is None: entity = get_entity_ids(session) - if entity.user_id in bot.config.superusers: + if is_superuser: return if entity.group_id: try: diff --git a/zhenxun/builtin_plugins/hooks/auth/auth_cost.py b/zhenxun/builtin_plugins/hooks/auth/auth_cost.py index 314bf254..8f2ea47e 100644 --- a/zhenxun/builtin_plugins/hooks/auth/auth_cost.py +++ b/zhenxun/builtin_plugins/hooks/auth/auth_cost.py @@ -8,7 +8,6 @@ from zhenxun.services.log import logger from .config import LOGGER_COMMAND, WARNING_THRESHOLD from .exception import SkipPluginException -from .utils import send_message DEFAULT_GOLD = 100 @@ -32,8 +31,10 @@ async def auth_cost( user_gold = user.gold if user else DEFAULT_GOLD if user_gold < plugin.cost_gold: """插件消耗金币不足""" - await send_message(session, f"金币不足..该功能需要{plugin.cost_gold}金币..") - raise SkipPluginException(f"{plugin.name}({plugin.module}) 金币限制...") + raise SkipPluginException( + f"{plugin.name}({plugin.module}) 金币限制...", + tip_message=f"金币不足..该功能需要{plugin.cost_gold}金币..", + ) return plugin.cost_gold finally: # 记录执行时间 diff --git a/zhenxun/builtin_plugins/hooks/auth/auth_plugin.py b/zhenxun/builtin_plugins/hooks/auth/auth_plugin.py index 0bc00dbb..77841967 100644 --- a/zhenxun/builtin_plugins/hooks/auth/auth_plugin.py +++ b/zhenxun/builtin_plugins/hooks/auth/auth_plugin.py @@ -11,7 +11,7 @@ from zhenxun.utils.enum import BlockType from .config import LOGGER_COMMAND, WARNING_THRESHOLD from .exception import IsSuperuserException, SkipPluginException -from .utils import freq, is_poke, send_message +from .utils import freq, is_poke def _get_group_block_sets( @@ -59,46 +59,41 @@ class GroupCheck: self.group_data and self.plugin.module in self.superuser_block_plugin_set ): - if freq.is_send_limit_message( + should_tip = freq.is_send_limit_message( self.plugin, self.group_id, self.is_poke - ): - await send_message( - self.session, - "超级管理员禁用了该群此功能...", - self.group_id, - background=True, - ) + ) raise SkipPluginException( f"{self.plugin.name}({self.plugin.module})" - f" 超级管理员禁用了该群此功能..." + f" 超级管理员禁用了该群此功能...", + tip_message=( + "超级管理员禁用了该群此功能..." if should_tip else None + ), + tip_check_tag=self.group_id if should_tip else None, + tip_background=should_tip, ) # 检查普通禁用 if self.group_data and self.plugin.module in self.block_plugin_set: - if freq.is_send_limit_message( + should_tip = freq.is_send_limit_message( self.plugin, self.group_id, self.is_poke - ): - await send_message( - self.session, - "该群未开启此功能...", - self.group_id, - background=True, - ) + ) raise SkipPluginException( - f"{self.plugin.name}({self.plugin.module}) 未开启此功能..." + f"{self.plugin.name}({self.plugin.module}) 未开启此功能...", + tip_message="该群未开启此功能..." if should_tip else None, + tip_check_tag=self.group_id if should_tip else None, + tip_background=should_tip, ) # 检查全局禁用 if self.plugin.block_type == BlockType.GROUP: - if freq.is_send_limit_message(self.plugin, self.group_id, self.is_poke): - await send_message( - self.session, - "该功能在群组中已被禁用...", - self.group_id, - background=True, - ) + should_tip = freq.is_send_limit_message( + self.plugin, self.group_id, self.is_poke + ) raise SkipPluginException( - f"{self.plugin.name}({self.plugin.module})该插件在群组中已被禁用..." + f"{self.plugin.name}({self.plugin.module})该插件在群组中已被禁用...", + tip_message="该功能在群组中已被禁用..." if should_tip else None, + tip_check_tag=self.group_id if should_tip else None, + tip_background=should_tip, ) finally: # 记录执行时间 @@ -131,14 +126,14 @@ class PluginCheck: IgnoredException: 忽略插件 """ if plugin.block_type == BlockType.PRIVATE: - if freq.is_send_limit_message(plugin, self.session.user.id, self.is_poke): - await send_message( - self.session, - "该功能在私聊中已被禁用...", - background=True, - ) + should_tip = freq.is_send_limit_message( + plugin, self.session.user.id, self.is_poke + ) raise SkipPluginException( - f"{plugin.name}({plugin.module}) 该插件在私聊中已被禁用..." + f"{plugin.name}({plugin.module}) 该插件在私聊中已被禁用...", + tip_message="该功能在私聊中已被禁用..." if should_tip else None, + tip_check_tag=self.session.user.id if should_tip else None, + tip_background=should_tip, ) async def check_global(self, plugin: PluginInfo): @@ -159,15 +154,12 @@ class PluginCheck: raise IsSuperuserException() sid = self.group_id or self.session.user.id - if freq.is_send_limit_message(plugin, sid, self.is_poke): - await send_message( - self.session, - "全局未开启此功能...", - sid, - background=True, - ) + should_tip = freq.is_send_limit_message(plugin, sid, self.is_poke) raise SkipPluginException( - f"{plugin.name}({plugin.module}) 全局未开启此功能..." + f"{plugin.name}({plugin.module}) 全局未开启此功能...", + tip_message="全局未开启此功能..." if should_tip else None, + tip_check_tag=sid if should_tip else None, + tip_background=should_tip, ) finally: # 记录执行时间 diff --git a/zhenxun/builtin_plugins/hooks/auth/exception.py b/zhenxun/builtin_plugins/hooks/auth/exception.py index 392a6718..77958a85 100644 --- a/zhenxun/builtin_plugins/hooks/auth/exception.py +++ b/zhenxun/builtin_plugins/hooks/auth/exception.py @@ -3,9 +3,21 @@ class IsSuperuserException(Exception): class SkipPluginException(Exception): - def __init__(self, info: str, *args: object) -> None: + def __init__( + self, + info: str, + *args: object, + tip_message: list | str | None = None, + tip_check_tag: str | None = None, + tip_background: bool = False, + tip_timeout: float | None = None, + ) -> None: super().__init__(*args) self.info = info + self.tip_message = tip_message + self.tip_check_tag = tip_check_tag + self.tip_background = tip_background + self.tip_timeout = tip_timeout def __str__(self) -> str: return self.info diff --git a/zhenxun/builtin_plugins/hooks/auth_checker.py b/zhenxun/builtin_plugins/hooks/auth_checker.py index a1ec5c56..203a37d5 100644 --- a/zhenxun/builtin_plugins/hooks/auth_checker.py +++ b/zhenxun/builtin_plugins/hooks/auth_checker.py @@ -36,7 +36,7 @@ from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.utils import get_entity_ids from .auth.auth_admin import auth_admin -from .auth.auth_ban import auth_ban, is_ban +from .auth.auth_ban import auth_ban from .auth.auth_bot import auth_bot from .auth.auth_cost import auth_cost from .auth.auth_group import auth_group @@ -49,6 +49,7 @@ from .auth.exception import ( PermissionExemption, SkipPluginException, ) +from .auth.utils import send_message AUTH_HOOKS_CONCURRENCY_LIMIT = 5 AUTH_DB_CONCURRENCY_LIMIT = 6 @@ -461,14 +462,26 @@ def _matcher_route_cache_key(event: Event) -> str: def _event_plain_text(event: Event) -> str: + def _normalize(text: str) -> str: + normalized = text.strip() + if not normalized: + return "" + # strip leading placeholders like "[reply:id=10004]撤回" + normalized = re.sub( + r"^(?:\s*(?:\[[^\]]*]|\<[^>]*>))+\s*", + "", + normalized, + ) + return normalized.strip() + with contextlib.suppress(Exception): # Use raw_message if available (OneBot v11) to get the original text # before nickname stripping. This ensures command matching works correctly # for commands like "真寻日报" when "真寻" is a bot nickname. raw = getattr(event, "raw_message", None) if isinstance(raw, str) and raw: - return raw.strip() - return (event.get_plaintext() or "").strip() + return _normalize(raw) + return _normalize(event.get_plaintext() or "") return "" @@ -1028,12 +1041,6 @@ async def _fetch_user_readonly( ) -async def _fetch_plugin(plugin_dao: DataAccess, module: str) -> PluginInfo | None: - return await with_timeout( - plugin_dao.safe_get_or_none(module=module), name="get_plugin" - ) - - async def get_plugin_and_user( module: str, user_id: str, @@ -1078,7 +1085,11 @@ async def get_plugin_and_user( async def get_plugin_cost( - bot: Bot, user: UserConsole | None, plugin: PluginInfo, session: Uninfo + user: UserConsole | None, + plugin: PluginInfo, + session: Uninfo, + *, + is_superuser: bool = False, ) -> int: """获取插件费用 @@ -1096,7 +1107,7 @@ async def get_plugin_cost( int: 调用插件金币费用 """ cost_gold = await with_timeout(auth_cost(user, plugin, session), name="auth_cost") - if session.user.id in bot.config.superusers: + if is_superuser: if plugin.plugin_type == PluginType.SUPERUSER: raise IsSuperuserException() if not plugin.limit_superuser: @@ -1184,30 +1195,6 @@ async def _leave_hooks_section(): HOOKS_ACTIVE_COUNT = max(HOOKS_ACTIVE_COUNT - 1, 0) -async def auth_ban_fast( - matcher: Matcher, event: Event, bot: Bot, session: Uninfo -) -> None: - """快速 ban 检测(仅使用内存缓存),用于前置快速裁决。""" - entity = get_entity_ids(session) - event_cache = _get_event_cache(event, session, entity) - if event_cache is not None and event_cache.get("ban_state") is True: - raise SkipPluginException("user or group banned (cached)") - if entity.user_id in bot.config.superusers: - if event_cache is not None: - event_cache["ban_state"] = False - return - if entity.group_id and await is_ban(None, entity.group_id): - if event_cache is not None: - event_cache["ban_state"] = True - raise SkipPluginException("group banned (fast)") - if entity.user_id and await is_ban(entity.user_id, entity.group_id): - if event_cache is not None: - event_cache["ban_state"] = True - raise SkipPluginException("user banned (fast)") - if event_cache is not None: - event_cache["ban_state"] = False - - async def route_precheck( matcher: Matcher, event: Event, @@ -1243,60 +1230,6 @@ async def route_precheck( return False -async def auth_precheck( - matcher: Matcher, - event: Event, - bot: Bot, - session: Uninfo, - message: UniMsg, -) -> None: - """轻量前置检查:命令路由 + 必要管理员权限。""" - module = matcher.plugin_name or "" - if not module: - return - if _is_hidden_plugin(matcher): - return - entity = get_entity_ids(session) - - if session.user.id in bot.config.superusers: - return - - plugin = cast(PluginInfo | None, await PluginInfoMemoryCache.get_by_module(module)) - if not plugin: - return - - if plugin.plugin_type == PluginType.SUPERUSER: - raise SkipPluginException("超级管理员权限不足...") - - if _needs_admin_check(plugin): - await LevelUserMemoryCache.ensure_fresh() - levels = await LevelUserMemoryCache.get_levels(session.user.id, entity.group_id) - await auth_admin(plugin, session, cached_levels=levels) - # 缓存 admin 检查结果到 event_cache,避免 auth() 重复执行 - event_cache = _get_event_cache(event, session, entity) - if event_cache is not None: - event_cache["admin_levels"] = levels - event_cache["admin_timeout"] = False - event_cache["admin_precheck_done"] = True - - -async def _call_auth_ban_compat( - matcher: Matcher, - bot: Bot, - session: Uninfo, - plugin: PluginInfo, - *, - entity, -) -> None: - """兼容旧签名 auth_ban(matcher, bot, session, plugin)。""" - try: - await auth_ban(matcher, bot, session, plugin, entity=entity) - except TypeError as exc: - if "unexpected keyword argument 'entity'" not in str(exc): - raise - await auth_ban(matcher, bot, session, plugin) - - async def auth( matcher: Matcher, event: Event, @@ -1309,7 +1242,7 @@ async def auth( event_cache: dict | None = None, text: str | None = None, route_modules: set[str] | None = None, - is_superuser: bool | None = None, + is_superuser: bool = False, ): """权限检查 @@ -1325,8 +1258,6 @@ async def auth( ignore_flag = False if entity is None: entity = get_entity_ids(session) - if is_superuser is None: - is_superuser = session.user.id in bot.config.superusers module = matcher.plugin_name or "" is_command_matcher = _is_command_matcher_class(type(matcher)) if event_cache is None: @@ -1451,8 +1382,12 @@ async def auth( else: ban_start = time.time() try: - await _call_auth_ban_compat( - matcher, bot, session, plugin, entity=entity + await auth_ban( + matcher, + session, + plugin, + entity=entity, + is_superuser=is_superuser, ) hook_recorder.set("auth_ban", f"{time.time() - ban_start:.3f}s") if event_cache is not None: @@ -1468,7 +1403,13 @@ async def auth( cost_start = time.time() try: cost_gold = await with_timeout( - get_plugin_cost(bot, user, plugin, session), name="get_plugin_cost" + get_plugin_cost( + user, + plugin, + session, + is_superuser=is_superuser, + ), + name="get_plugin_cost", ) hook_recorder.set("cost_gold", f"{time.time() - cost_start:.3f}s") except asyncio.TimeoutError: @@ -1622,6 +1563,20 @@ async def auth( except SkipPluginException as e: LimitManager.unblock(module, entity.user_id, entity.group_id, entity.channel_id) + if e.tip_message: + try: + tip_coro = send_message( + session, + e.tip_message, + e.tip_check_tag, + background=e.tip_background, + ) + if e.tip_timeout and not e.tip_background: + await asyncio.wait_for(tip_coro, timeout=e.tip_timeout) + else: + await tip_coro + except asyncio.TimeoutError: + logger.error("发送权限提示超时", LOGGER_COMMAND, session=session) logger.info(str(e), LOGGER_COMMAND, session=session) ignore_flag = True auth_allowed = False diff --git a/zhenxun/builtin_plugins/hooks/auth_hook.py b/zhenxun/builtin_plugins/hooks/auth_hook.py index d28acb18..8a9b1337 100644 --- a/zhenxun/builtin_plugins/hooks/auth_hook.py +++ b/zhenxun/builtin_plugins/hooks/auth_hook.py @@ -1,4 +1,3 @@ -import asyncio import contextlib import time @@ -30,13 +29,6 @@ from .auth_checker import ( _SKIP_AUTH_PLUGINS = {"chat_history", "chat_message"} _BOT_CONNECT_TS: float | None = None -_AUTH_QUEUE_MAXSIZE = 200 -_AUTH_QUEUE: asyncio.Queue[tuple[Matcher, Event, Bot, Uninfo, UniMsg | None]] = ( - asyncio.Queue(maxsize=_AUTH_QUEUE_MAXSIZE) -) -_AUTH_QUEUE_STARTED = False -_AUTH_WORKERS: list[asyncio.Task] = [] -_LAST_DROP_LOG = 0.0 driver = get_driver() register_runtime_bootstrap(driver) @@ -49,27 +41,6 @@ async def _mark_bot_connected(bot: Bot): _BOT_CONNECT_TS = time.time() -async def _auth_worker(worker_id: int) -> None: - while True: - matcher, event, bot, session, message = await _AUTH_QUEUE.get() - try: - await auth( - matcher, - event, - bot, - session, - message, - skip_ban=True, - ) - except IgnoredException: - pass - except Exception as exc: - if not is_overloaded(): - logger.error("async auth failed", LOGGER_COMMAND, e=exc) - finally: - _AUTH_QUEUE.task_done() - - def _extract_plain_text(message: UniMsg | None, event: Event) -> str: if message is not None: with contextlib.suppress(Exception): @@ -82,33 +53,12 @@ def _extract_plain_text(message: UniMsg | None, event: Event) -> str: @driver.on_startup -async def _start_auth_queue(): - global _AUTH_QUEUE_STARTED - if _AUTH_QUEUE_STARTED: - return - _AUTH_QUEUE_STARTED = True - worker_count = max(1, min(6, _AUTH_QUEUE_MAXSIZE // 50)) - for idx in range(worker_count): - _AUTH_WORKERS.append(asyncio.create_task(_auth_worker(idx))) +async def _start_auth_runtime_tasks(): await start_auth_runtime_tasks() @driver.on_shutdown -async def _stop_auth_queue(): - global _AUTH_QUEUE_STARTED - _AUTH_QUEUE_STARTED = False - workers = _AUTH_WORKERS.copy() - _AUTH_WORKERS.clear() - for task in workers: - task.cancel() - if workers: - await asyncio.gather(*workers, return_exceptions=True) - - while not _AUTH_QUEUE.empty(): - with contextlib.suppress(Exception): - _AUTH_QUEUE.get_nowait() - _AUTH_QUEUE.task_done() - +async def _stop_auth_runtime_tasks(): await stop_auth_runtime_tasks() diff --git a/zhenxun/builtin_plugins/init/init_plugin.py b/zhenxun/builtin_plugins/init/init_plugin.py index 87ad9fc8..4b912dfd 100644 --- a/zhenxun/builtin_plugins/init/init_plugin.py +++ b/zhenxun/builtin_plugins/init/init_plugin.py @@ -158,7 +158,7 @@ async def _(): # if limit_create: # await PluginLimit.bulk_create(limit_create, 10) await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True) - await PluginInfo.filter(module_path__not_in=load_plugin).delete() + await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False) from zhenxun.services.cache.runtime_cache import PluginInfoMemoryCache await PluginInfoMemoryCache.refresh() diff --git a/zhenxun/builtin_plugins/plugin_store/data_source.py b/zhenxun/builtin_plugins/plugin_store/data_source.py index 08fc385d..999d2fc0 100644 --- a/zhenxun/builtin_plugins/plugin_store/data_source.py +++ b/zhenxun/builtin_plugins/plugin_store/data_source.py @@ -44,6 +44,30 @@ def row_style(column: str, text: str) -> RowStyle: class StoreManager: + @classmethod + def _resolve_local_plugin_path( + cls, plugin_info: StorePluginInfo, *, is_external: bool + ) -> Path: + """将商店插件信息映射到本地插件文件/目录路径。""" + plugin_name = plugin_info.module_path.split(".")[-1] or plugin_info.module + if is_external and plugin_info.is_dir and plugin_info.module_path == ".": + return BASE_PATH / "plugins" / plugin_name + + relative_parts = [part for part in plugin_info.module_path.split(".") if part] + relative_path = Path(*relative_parts) if relative_parts else Path(plugin_name) + path = BASE_PATH.parent / relative_path + if plugin_info.is_dir: + return path + return path.parent / f"{plugin_name}.py" + + @classmethod + def _is_plugin_installed( + cls, plugin_info: StorePluginInfo, *, is_external: bool + ) -> bool: + return cls._resolve_local_plugin_path( + plugin_info, is_external=is_external + ).exists() + @classmethod @cached(60) async def get_data(cls) -> tuple[list[StorePluginInfo], list[StorePluginInfo]]: @@ -98,13 +122,16 @@ class StoreManager: return suc_plugin.get(module) and plugin_info.version == suc_plugin[module] @classmethod - async def get_loaded_plugins(cls, *args) -> list[tuple[str, str]]: - """获取已加载的插件 + async def get_installed_plugins(cls) -> dict[str, str]: + """获取已安装插件的模块与版本。 返回: - list[str]: 已加载的插件 + dict[str, str]: 模块 -> 版本 """ - return await PluginInfo.get_plugins_values_list(*args, load_status=True) + db_plugin_list = await PluginInfo.get_plugins_values_list( + "module", "version", load_status=True, filter_parent=False + ) + return {p[0]: (p[1] or "0.1") for p in db_plugin_list} @classmethod async def get_plugins_info(cls) -> list[BuildImage] | str: @@ -115,8 +142,7 @@ class StoreManager: """ plugin_list, extra_plugin_list = await cls.get_data() column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] - db_plugin_list = await cls.get_loaded_plugins("module", "version") - suc_plugin = {p[0]: (p[1] or "0.1") for p in db_plugin_list} + suc_plugin = await cls.get_installed_plugins() index = 0 data_list = [] extra_data_list = [] @@ -190,7 +216,6 @@ class StoreManager: plugin_list, extra_plugin_list = await cls.get_data() plugin_info = None is_external = False - db_plugin_list = await cls.get_loaded_plugins("module") try: plugin_key = await cls._resolve_plugin_key(index_or_module) except PluginStoreException: @@ -211,7 +236,7 @@ class StoreManager: plugin_info = p break - modules = [p[0] for p in db_plugin_list] + installed_modules = set((await cls.get_installed_plugins()).keys()) if is_remove: # 商店列表中找不到时,从数据库构建最小插件信息 @@ -237,13 +262,17 @@ class StoreManager: is_dir=_path.is_dir(), ) is_external = True - if plugin_info.module not in modules: + if plugin_info.module not in installed_modules: raise PluginStoreException(f"插件 {plugin_info.name} 未安装,无法移除") if plugin_obj := await PluginInfo.get_plugin( - module=plugin_info.module, plugin_type=PluginType.PARENT + module=plugin_info.module, + plugin_type=PluginType.PARENT, + load_status=True, ): plugin_info.module_path = plugin_obj.module_path - elif plugin_obj := await PluginInfo.get_plugin(module=plugin_info.module): + elif plugin_obj := await PluginInfo.get_plugin( + module=plugin_info.module, load_status=True + ): plugin_info.module_path = plugin_obj.module_path return plugin_info, is_external @@ -251,11 +280,11 @@ class StoreManager: raise PluginStoreException(f"插件不存在: {plugin_key}") if is_update: - if plugin_info.module not in modules: + if plugin_info.module not in installed_modules: raise PluginStoreException(f"插件 {plugin_info.name} 未安装,无法更新") return plugin_info, is_external - if plugin_info.module in modules: + if plugin_info.module in installed_modules: raise PluginStoreException(f"插件 {plugin_info.name} 已安装,无需重复安装") return plugin_info, is_external @@ -391,11 +420,8 @@ class StoreManager: str: 返回消息 """ plugin_info, _ = await cls.get_plugin_by_value(index_or_module, is_remove=True) - module_path = plugin_info.module_path - module = module_path.split(".")[-1] - path = BASE_PATH.parent / Path(module_path.replace(".", os.sep)) - if not plugin_info.is_dir: - path = path.parent / f"{module}.py" + is_external = not plugin_info.module_path.startswith("zhenxun.") + path = cls._resolve_local_plugin_path(plugin_info, is_external=is_external) if not path.exists(): return f"插件 {plugin_info.name} 不存在..." logger.debug(f"尝试移除插件 {plugin_info.name} 文件: {path}", LOG_COMMAND) @@ -404,7 +430,14 @@ class StoreManager: shutil.rmtree(path, onerror=win_on_rm_error) else: path.unlink() - await PluginInitManager.remove(module_path) + await PluginInitManager.remove(plugin_info.module_path) + plugin_records = await PluginInfo.get_plugins( + load_status=None, + filter_parent=False, + module_path=plugin_info.module_path, + ) + for plugin_record in plugin_records: + await plugin_record.delete() return f"插件 {plugin_info.name} 移除成功! 重启后生效" @classmethod @@ -419,8 +452,7 @@ class StoreManager: """ plugin_list, extra_plugin_list = await cls.get_data() all_plugin_list = plugin_list + extra_plugin_list - db_plugin_list = await cls.get_loaded_plugins("module", "version") - suc_plugin = {p[0]: (p[1] or "Unknown") for p in db_plugin_list} + suc_plugin = await cls.get_installed_plugins() filtered_data = [ (id, plugin_info) for id, plugin_info in enumerate(all_plugin_list) @@ -463,8 +495,7 @@ class StoreManager: """ plugin_info, is_external = await cls.get_plugin_by_value(index_or_module, True) logger.info(f"尝试更新插件 {plugin_info.name}", LOG_COMMAND) - db_plugin_list = await cls.get_loaded_plugins("module", "version") - suc_plugin = {p[0]: (p[1] or "Unknown") for p in db_plugin_list} + suc_plugin = await cls.get_installed_plugins() logger.debug(f"当前插件列表: {suc_plugin}", LOG_COMMAND) if cls.check_version_is_new(plugin_info, suc_plugin): return f"插件 {plugin_info.name} 已是最新版本" @@ -493,11 +524,10 @@ class StoreManager: update_success_list = [] result = "--已更新{}个插件 {}个失败 {}个成功--" logger.info(f"尝试更新全部插件 {plugin_name_list}", LOG_COMMAND) + suc_plugin = await cls.get_installed_plugins() for plugin_info in all_plugin_list: try: - db_plugin_list = await cls.get_loaded_plugins("module", "version") - suc_plugin = {p[0]: (p[1] or "Unknown") for p in db_plugin_list} - if plugin_info.module not in [p[0] for p in db_plugin_list]: + if plugin_info.module not in suc_plugin: logger.debug( f"插件 {plugin_info.name}({plugin_info.module}) 未安装,跳过", LOG_COMMAND, diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/store.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/store.py index 6c821c42..44f67b86 100644 --- a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/store.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/store.py @@ -3,7 +3,6 @@ from fastapi.responses import JSONResponse from nonebot import require from nonebot.compat import model_dump -from zhenxun.models.plugin_info import PluginInfo from zhenxun.services.log import logger from ....base_model import Result @@ -30,7 +29,7 @@ async def _() -> Result[dict]: {**model_dump(plugin), "name": plugin.name, "id": idx} for idx, plugin in enumerate(plugin_list + extra_plugin_list) ] - modules = await PluginInfo.get_plugins_values_list("module", load_status=True) + modules = list((await StoreManager.get_installed_plugins()).keys()) return Result.ok({"install_module": modules, "plugin_list": plugin_list}) except Exception as e: logger.error("获取插件商店插件信息失败", "WebUi", e=e) diff --git a/zhenxun/builtin_plugins/withdraw.py b/zhenxun/builtin_plugins/withdraw.py index ef10d592..eb0cd0dc 100644 --- a/zhenxun/builtin_plugins/withdraw.py +++ b/zhenxun/builtin_plugins/withdraw.py @@ -1,4 +1,6 @@ from nonebot.adapters import Bot, Event +from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot +from nonebot.adapters.onebot.v12 import Bot as OneBotV12Bot from nonebot.plugin import PluginMetadata from nonebot.rule import Rule from nonebot_plugin_alconna import Alconna, Arparma, on_alconna @@ -48,18 +50,27 @@ def reply_check() -> Rule: _matcher = on_alconna(Alconna("撤回"), priority=5, block=True, rule=reply_check()) +def _resolve_delete_message_id(bot: Bot, reply_id: str | int): + if isinstance(bot, OneBotV11Bot): + return int(reply_id) + if isinstance(bot, OneBotV12Bot): + return str(reply_id) + return reply_id + + @_matcher.handle() async def _(bot: Bot, event: Event, session: Uninfo, arparma: Arparma): if reply := await reply_fetch(event, bot): + message_id = _resolve_delete_message_id(bot, reply.id) if session.user.id in bot.config.superusers: try: - await bot.delete_msg(message_id=reply.id) + await bot.delete_msg(message_id=message_id) logger.info("撤回消息", arparma.header_result, session=session) except Exception: await MessageUtils.build_message("撤回失败,可能消息已过期...").send() elif MessageManager.check(session.user.id, reply.id): try: - await bot.delete_msg(message_id=reply.id) + await bot.delete_msg(message_id=message_id) logger.info("撤回消息", arparma.header_result, session=session) except Exception: await MessageUtils.build_message("撤回失败,可能消息已过期...").send()