From bc8e1659ae70231bb447e8a43543a7f8afda245c Mon Sep 17 00:00:00 2001 From: HibiKier <45528451+HibiKier@users.noreply.github.com> Date: Fri, 6 Feb 2026 16:46:01 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(plugins):=20Enhance=20PluginEx?= =?UTF-8?q?traData=20with=20ignore=5Fstatistics=20and=20limits=20features?= =?UTF-8?q?=20(#2098)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added ignore_statistics field to PluginExtraData to control call statistics. - Introduced limits parameter in PluginExtraData for rate limiting plugin usage. - Updated relevant plugins to utilize the new features for better control over plugin statistics and usage limits. --- zhenxun/builtin_plugins/about.py | 9 ++++-- .../admin/plugin_switch/_data_source.py | 7 ++++- zhenxun/builtin_plugins/init/init_plugin.py | 1 + .../statistics/statistics_hook.py | 14 +++++---- .../api/tabs/plugin_manage/data_source.py | 5 ++++ zhenxun/configs/utils/models.py | 2 ++ zhenxun/models/plugin_info.py | 29 +++++++++++++++++++ zhenxun/services/cache/runtime_cache.py | 26 +++++++++++++++++ 8 files changed, 85 insertions(+), 8 deletions(-) diff --git a/zhenxun/builtin_plugins/about.py b/zhenxun/builtin_plugins/about.py index 31c77bc7..e50156df 100644 --- a/zhenxun/builtin_plugins/about.py +++ b/zhenxun/builtin_plugins/about.py @@ -7,7 +7,7 @@ from nonebot_plugin_alconna import Alconna, Arparma, on_alconna from nonebot_plugin_uninfo import Uninfo from zhenxun.configs.path_config import DATA_PATH -from zhenxun.configs.utils import PluginExtraData +from zhenxun.configs.utils import PluginCdBlock, PluginExtraData from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import PlatformUtils @@ -19,7 +19,12 @@ __plugin_meta__ = PluginMetadata( 指令: 关于 """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1", menu_type="其他").to_dict(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + menu_type="其他", + limits=[PluginCdBlock(cd=10, result="每10秒只能查看一次哦~")], + ).to_dict(), ) diff --git a/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py b/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py index 9e21947f..c9975ee6 100644 --- a/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py +++ b/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py @@ -4,7 +4,10 @@ from zhenxun.models.group_console import GroupConsole from zhenxun.models.plugin_info import PluginInfo from zhenxun.models.task_info import TaskInfo from zhenxun.services.cache import CacheRoot -from zhenxun.services.cache.runtime_cache import TaskInfoMemoryCache +from zhenxun.services.cache.runtime_cache import ( + PluginInfoMemoryCache, + TaskInfoMemoryCache, +) from zhenxun.utils.common_utils import CommonUtils from zhenxun.utils.enum import BlockType, CacheType, PluginType from zhenxun.utils.exception import GroupInfoNotFound @@ -181,6 +184,7 @@ class PluginManager: await PluginInfo.filter(plugin_type=PluginType.NORMAL).update( default_status=status ) + await PluginInfoMemoryCache.refresh() return f"成功将所有功能进群默认状态修改为: {'开启' if status else '关闭'}" if group_id: if group := await GroupConsole.get_group_db(group_id=group_id): @@ -203,6 +207,7 @@ class PluginManager: status=status, block_type=None if status else BlockType.ALL ) await CacheRoot.invalidate_cache(CacheType.PLUGINS) + await PluginInfoMemoryCache.refresh() return f"成功将所有功能全局状态修改为: {'开启' if status else '关闭'}" @classmethod diff --git a/zhenxun/builtin_plugins/init/init_plugin.py b/zhenxun/builtin_plugins/init/init_plugin.py index f5e1b09f..96163d03 100644 --- a/zhenxun/builtin_plugins/init/init_plugin.py +++ b/zhenxun/builtin_plugins/init/init_plugin.py @@ -80,6 +80,7 @@ async def _handle_setting( ignore_prompt=extra_data.ignore_prompt, parent=(plugin.parent_plugin.module_name if plugin.parent_plugin else None), impression=setting.impression, + ignore_statistics=extra_data.ignore_statistics, ) ) if extra_data.limits: diff --git a/zhenxun/builtin_plugins/statistics/statistics_hook.py b/zhenxun/builtin_plugins/statistics/statistics_hook.py index 13c67ce9..cef53e86 100644 --- a/zhenxun/builtin_plugins/statistics/statistics_hook.py +++ b/zhenxun/builtin_plugins/statistics/statistics_hook.py @@ -6,7 +6,7 @@ from nonebot.matcher import Matcher from nonebot.message import run_postprocessor from nonebot.plugin import PluginMetadata from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession +from nonebot_plugin_uninfo import Uninfo from zhenxun.configs.utils import PluginExtraData from zhenxun.models.plugin_info import PluginInfo @@ -15,6 +15,7 @@ from zhenxun.services.cache.runtime_cache import PluginInfoMemoryCache from zhenxun.services.log import logger from zhenxun.services.message_load import should_pause_tasks from zhenxun.utils.enum import PluginType +from zhenxun.utils.utils import get_entity_ids __plugin_meta__ = PluginMetadata( name="功能调用统计", @@ -33,25 +34,28 @@ async def _( matcher: Matcher, exception: Exception | None, bot: Bot, - session: EventSession, + session: Uninfo, event: Event, ): if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent): """过滤除poke外的notice""" return - if session.id1 and matcher.plugin: + if matcher.plugin: + entity = get_entity_ids(session) plugin = PluginInfoMemoryCache.get_by_module_path(matcher.plugin.module_name) if not plugin: plugin = await PluginInfo.get_plugin(module_path=matcher.plugin.module_name) if plugin: PluginInfoMemoryCache.set_plugin(plugin) + if plugin and plugin.ignore_statistics: + return plugin_type = plugin.plugin_type if plugin else None if plugin_type == PluginType.NORMAL: logger.debug(f"提交调用记录: {matcher.plugin_name}...", session=session) TEMP_LIST.append( Statistics( - user_id=session.id1, - group_id=session.id3 or session.id2, + user_id=entity.user_id, + group_id=entity.group_id, plugin_name=matcher.plugin_name, create_time=datetime.now(), bot_id=bot.self_id, diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py index df54752e..3d90b6ab 100644 --- a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py @@ -7,6 +7,7 @@ from tortoise.exceptions import DoesNotExist from zhenxun.configs.config import Config from zhenxun.configs.utils import ConfigGroup from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo +from zhenxun.services.cache.runtime_cache import PluginInfoMemoryCache from zhenxun.utils.enum import BlockType, PluginType from .model import ( @@ -159,6 +160,8 @@ class ApiDataSource: plugins_to_update_other_fields, list(other_update_fields) ) bulk_updated_count = len(plugins_to_update_other_fields) + for plugin in plugins_to_update_other_fields: + await PluginInfoMemoryCache.upsert_from_model(plugin) except Exception as e_bulk: errors.append( { @@ -243,6 +246,8 @@ class ApiDataSource: updated_count = await DbPluginInfo.filter(menu_type=old_name).update( menu_type=new_name ) + if updated_count: + await PluginInfoMemoryCache.refresh() return {"success": True, "updated_count": updated_count} except Exception as e: # 可以添加更详细的日志记录 diff --git a/zhenxun/configs/utils/models.py b/zhenxun/configs/utils/models.py index 2944f302..baeb65c0 100644 --- a/zhenxun/configs/utils/models.py +++ b/zhenxun/configs/utils/models.py @@ -261,6 +261,8 @@ class PluginExtraData(BaseModel): """常用sql""" is_show: bool = True """是否显示在菜单中""" + ignore_statistics: bool = False + """是否不统计调用次数""" smart_tools: list[AICallableTag] | None = None """智能模式函数工具集""" introduction: str | None = None diff --git a/zhenxun/models/plugin_info.py b/zhenxun/models/plugin_info.py index c25f1de8..9a32e221 100644 --- a/zhenxun/models/plugin_info.py +++ b/zhenxun/models/plugin_info.py @@ -3,6 +3,7 @@ from typing_extensions import Self from tortoise import fields from zhenxun.models.plugin_limit import PluginLimit # noqa: F401 +from zhenxun.services.cache.runtime_cache import PluginInfoMemoryCache from zhenxun.services.db_context import Model from zhenxun.utils.enum import BlockType, CacheType, PluginType @@ -52,6 +53,10 @@ class PluginInfo(Model): """父插件""" is_show = fields.BooleanField(default=True, description="是否显示在帮助中") """是否显示在帮助中""" + ignore_statistics = fields.BooleanField( + default=False, description="是否不统计调用次数" + ) + """是否不统计调用次数""" impression = fields.FloatField(default=0, description="插件好感度限制") """插件好感度限制""" @@ -64,6 +69,28 @@ class PluginInfo(Model): cache_key_field = "module" """缓存键字段""" + @classmethod + async def create(cls, *args, **kwargs): + result = await super().create(*args, **kwargs) + await PluginInfoMemoryCache.upsert_from_model(result) + return result + + @classmethod + async def update_or_create(cls, *args, **kwargs): + result = await super().update_or_create(*args, **kwargs) + await PluginInfoMemoryCache.upsert_from_model(result[0]) + return result + + async def save(self, *args, **kwargs): + await super().save(*args, **kwargs) + await PluginInfoMemoryCache.upsert_from_model(self) + + async def delete(self, *args, **kwargs): + module = self.module + module_path = self.module_path + await super().delete(*args, **kwargs) + await PluginInfoMemoryCache.remove(module, module_path) + @classmethod async def get_plugin( cls, load_status: bool = True, filter_parent: bool = True, **kwargs @@ -110,4 +137,6 @@ class PluginInfo(Model): "ALTER TABLE plugin_info ADD COLUMN ignore_prompt boolean DEFAULT false;", "ALTER TABLE plugin_info ADD COLUMN impression float DEFAULT 0;", "CREATE INDEX idx_plugin_info_module ON plugin_info(module);", + "ALTER TABLE plugin_info ADD COLUMN ignore_statistics" + " boolean DEFAULT false;", ] diff --git a/zhenxun/services/cache/runtime_cache.py b/zhenxun/services/cache/runtime_cache.py index 55b1249d..9ce9d68f 100644 --- a/zhenxun/services/cache/runtime_cache.py +++ b/zhenxun/services/cache/runtime_cache.py @@ -688,11 +688,37 @@ class PluginInfoMemoryCache: cls._by_module[plugin.module] = plugin if getattr(plugin, "module_path", None): cls._by_module_path[plugin.module_path] = plugin + cls._loaded = True + cls._last_refresh = time.time() @classmethod def remove_by_module(cls, module: str) -> None: cls._by_module.pop(module, None) + @classmethod + async def upsert_from_model(cls, plugin) -> None: + if not plugin: + return + async with cls._lock: + if getattr(plugin, "module", None): + cls._by_module[plugin.module] = plugin + if getattr(plugin, "module_path", None): + cls._by_module_path[plugin.module_path] = plugin + cls._loaded = True + cls._last_refresh = time.time() + + @classmethod + async def remove( + cls, module: str | None = None, module_path: str | None = None + ) -> None: + if not module and not module_path: + return + async with cls._lock: + if module: + cls._by_module.pop(module, None) + if module_path: + cls._by_module_path.pop(module_path, None) + @classmethod async def _refresh_loop(cls, interval: int) -> None: while True: