✨ feat(plugins): Enhance PluginExtraData with ignore_statistics and limits features (#2098)

- 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.
This commit is contained in:
HibiKier
2026-02-06 16:46:01 +08:00
committed by GitHub
parent 5c067bcf04
commit bc8e1659ae
8 changed files with 85 additions and 8 deletions
+7 -2
View File
@@ -7,7 +7,7 @@ from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_uninfo import Uninfo from nonebot_plugin_uninfo import Uninfo
from zhenxun.configs.path_config import DATA_PATH 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.services.log import logger
from zhenxun.utils.message import MessageUtils from zhenxun.utils.message import MessageUtils
from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.platform import PlatformUtils
@@ -19,7 +19,12 @@ __plugin_meta__ = PluginMetadata(
指令: 指令:
关于 关于
""".strip(), """.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(),
) )
@@ -4,7 +4,10 @@ from zhenxun.models.group_console import GroupConsole
from zhenxun.models.plugin_info import PluginInfo from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.task_info import TaskInfo from zhenxun.models.task_info import TaskInfo
from zhenxun.services.cache import CacheRoot 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.common_utils import CommonUtils
from zhenxun.utils.enum import BlockType, CacheType, PluginType from zhenxun.utils.enum import BlockType, CacheType, PluginType
from zhenxun.utils.exception import GroupInfoNotFound from zhenxun.utils.exception import GroupInfoNotFound
@@ -181,6 +184,7 @@ class PluginManager:
await PluginInfo.filter(plugin_type=PluginType.NORMAL).update( await PluginInfo.filter(plugin_type=PluginType.NORMAL).update(
default_status=status default_status=status
) )
await PluginInfoMemoryCache.refresh()
return f"成功将所有功能进群默认状态修改为: {'开启' if status else '关闭'}" return f"成功将所有功能进群默认状态修改为: {'开启' if status else '关闭'}"
if group_id: if group_id:
if group := await GroupConsole.get_group_db(group_id=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 status=status, block_type=None if status else BlockType.ALL
) )
await CacheRoot.invalidate_cache(CacheType.PLUGINS) await CacheRoot.invalidate_cache(CacheType.PLUGINS)
await PluginInfoMemoryCache.refresh()
return f"成功将所有功能全局状态修改为: {'开启' if status else '关闭'}" return f"成功将所有功能全局状态修改为: {'开启' if status else '关闭'}"
@classmethod @classmethod
@@ -80,6 +80,7 @@ async def _handle_setting(
ignore_prompt=extra_data.ignore_prompt, ignore_prompt=extra_data.ignore_prompt,
parent=(plugin.parent_plugin.module_name if plugin.parent_plugin else None), parent=(plugin.parent_plugin.module_name if plugin.parent_plugin else None),
impression=setting.impression, impression=setting.impression,
ignore_statistics=extra_data.ignore_statistics,
) )
) )
if extra_data.limits: if extra_data.limits:
@@ -6,7 +6,7 @@ from nonebot.matcher import Matcher
from nonebot.message import run_postprocessor from nonebot.message import run_postprocessor
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from nonebot_plugin_apscheduler import scheduler 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.configs.utils import PluginExtraData
from zhenxun.models.plugin_info import PluginInfo 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.log import logger
from zhenxun.services.message_load import should_pause_tasks from zhenxun.services.message_load import should_pause_tasks
from zhenxun.utils.enum import PluginType from zhenxun.utils.enum import PluginType
from zhenxun.utils.utils import get_entity_ids
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
name="功能调用统计", name="功能调用统计",
@@ -33,25 +34,28 @@ async def _(
matcher: Matcher, matcher: Matcher,
exception: Exception | None, exception: Exception | None,
bot: Bot, bot: Bot,
session: EventSession, session: Uninfo,
event: Event, event: Event,
): ):
if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent): if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent):
"""过滤除poke外的notice""" """过滤除poke外的notice"""
return 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) plugin = PluginInfoMemoryCache.get_by_module_path(matcher.plugin.module_name)
if not plugin: if not plugin:
plugin = await PluginInfo.get_plugin(module_path=matcher.plugin.module_name) plugin = await PluginInfo.get_plugin(module_path=matcher.plugin.module_name)
if plugin: if plugin:
PluginInfoMemoryCache.set_plugin(plugin) PluginInfoMemoryCache.set_plugin(plugin)
if plugin and plugin.ignore_statistics:
return
plugin_type = plugin.plugin_type if plugin else None plugin_type = plugin.plugin_type if plugin else None
if plugin_type == PluginType.NORMAL: if plugin_type == PluginType.NORMAL:
logger.debug(f"提交调用记录: {matcher.plugin_name}...", session=session) logger.debug(f"提交调用记录: {matcher.plugin_name}...", session=session)
TEMP_LIST.append( TEMP_LIST.append(
Statistics( Statistics(
user_id=session.id1, user_id=entity.user_id,
group_id=session.id3 or session.id2, group_id=entity.group_id,
plugin_name=matcher.plugin_name, plugin_name=matcher.plugin_name,
create_time=datetime.now(), create_time=datetime.now(),
bot_id=bot.self_id, bot_id=bot.self_id,
@@ -7,6 +7,7 @@ from tortoise.exceptions import DoesNotExist
from zhenxun.configs.config import Config from zhenxun.configs.config import Config
from zhenxun.configs.utils import ConfigGroup from zhenxun.configs.utils import ConfigGroup
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo 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 zhenxun.utils.enum import BlockType, PluginType
from .model import ( from .model import (
@@ -159,6 +160,8 @@ class ApiDataSource:
plugins_to_update_other_fields, list(other_update_fields) plugins_to_update_other_fields, list(other_update_fields)
) )
bulk_updated_count = len(plugins_to_update_other_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: except Exception as e_bulk:
errors.append( errors.append(
{ {
@@ -243,6 +246,8 @@ class ApiDataSource:
updated_count = await DbPluginInfo.filter(menu_type=old_name).update( updated_count = await DbPluginInfo.filter(menu_type=old_name).update(
menu_type=new_name menu_type=new_name
) )
if updated_count:
await PluginInfoMemoryCache.refresh()
return {"success": True, "updated_count": updated_count} return {"success": True, "updated_count": updated_count}
except Exception as e: except Exception as e:
# 可以添加更详细的日志记录 # 可以添加更详细的日志记录
+2
View File
@@ -261,6 +261,8 @@ class PluginExtraData(BaseModel):
"""常用sql""" """常用sql"""
is_show: bool = True is_show: bool = True
"""是否显示在菜单中""" """是否显示在菜单中"""
ignore_statistics: bool = False
"""是否不统计调用次数"""
smart_tools: list[AICallableTag] | None = None smart_tools: list[AICallableTag] | None = None
"""智能模式函数工具集""" """智能模式函数工具集"""
introduction: str | None = None introduction: str | None = None
+29
View File
@@ -3,6 +3,7 @@ from typing_extensions import Self
from tortoise import fields from tortoise import fields
from zhenxun.models.plugin_limit import PluginLimit # noqa: F401 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.services.db_context import Model
from zhenxun.utils.enum import BlockType, CacheType, PluginType from zhenxun.utils.enum import BlockType, CacheType, PluginType
@@ -52,6 +53,10 @@ class PluginInfo(Model):
"""父插件""" """父插件"""
is_show = fields.BooleanField(default=True, description="是否显示在帮助中") is_show = fields.BooleanField(default=True, description="是否显示在帮助中")
"""是否显示在帮助中""" """是否显示在帮助中"""
ignore_statistics = fields.BooleanField(
default=False, description="是否不统计调用次数"
)
"""是否不统计调用次数"""
impression = fields.FloatField(default=0, description="插件好感度限制") impression = fields.FloatField(default=0, description="插件好感度限制")
"""插件好感度限制""" """插件好感度限制"""
@@ -64,6 +69,28 @@ class PluginInfo(Model):
cache_key_field = "module" 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 @classmethod
async def get_plugin( async def get_plugin(
cls, load_status: bool = True, filter_parent: bool = True, **kwargs 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 ignore_prompt boolean DEFAULT false;",
"ALTER TABLE plugin_info ADD COLUMN impression float DEFAULT 0;", "ALTER TABLE plugin_info ADD COLUMN impression float DEFAULT 0;",
"CREATE INDEX idx_plugin_info_module ON plugin_info(module);", "CREATE INDEX idx_plugin_info_module ON plugin_info(module);",
"ALTER TABLE plugin_info ADD COLUMN ignore_statistics"
" boolean DEFAULT false;",
] ]
+26
View File
@@ -688,11 +688,37 @@ class PluginInfoMemoryCache:
cls._by_module[plugin.module] = plugin cls._by_module[plugin.module] = plugin
if getattr(plugin, "module_path", None): if getattr(plugin, "module_path", None):
cls._by_module_path[plugin.module_path] = plugin cls._by_module_path[plugin.module_path] = plugin
cls._loaded = True
cls._last_refresh = time.time()
@classmethod @classmethod
def remove_by_module(cls, module: str) -> None: def remove_by_module(cls, module: str) -> None:
cls._by_module.pop(module, 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 @classmethod
async def _refresh_loop(cls, interval: int) -> None: async def _refresh_loop(cls, interval: int) -> None:
while True: while True: