From a6ddb726d3b14a09482a731ca894071cb086f5cf Mon Sep 17 00:00:00 2001 From: ChthollyWn <85938618+ChthollyWn@users.noreply.github.com> Date: Mon, 24 Feb 2025 09:28:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8F=92=E4=BB=B6=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E6=A8=A1=E5=BC=8F=E9=80=82=E9=85=8D=20(#1850)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 新增插件智能模式适配 * :rotating_light: auto fix by pre-commit hooks * 更改类名,命名更清晰 * :art: 添加模块化参数 * :art: AI模块化修改 * :adhesive_bandage: 道具调用修复 * :adhesive_bandage: 修复商品使用前检测 * :sparkles: retry增加参数适配 * :sparkles: 修改道具使用函数参数传递 * :sparkles: 捕获道具无法使用异常 * :bug: 添加依赖require * :bug: 修复插件使用问题 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: HibiKier <45528451+HibiKier@users.noreply.github.com> --- zhenxun/builtin_plugins/shop/__init__.py | 7 ++ zhenxun/builtin_plugins/shop/_data_source.py | 73 +++++++++++--------- zhenxun/configs/utils/__init__.py | 36 ++++++++++ zhenxun/services/__init__.py | 1 + zhenxun/utils/decorator/retry.py | 20 ++++-- 5 files changed, 97 insertions(+), 40 deletions(-) diff --git a/zhenxun/builtin_plugins/shop/__init__.py b/zhenxun/builtin_plugins/shop/__init__.py index 32897811..89282d63 100644 --- a/zhenxun/builtin_plugins/shop/__init__.py +++ b/zhenxun/builtin_plugins/shop/__init__.py @@ -18,6 +18,7 @@ from nonebot_plugin_uninfo import Uninfo from zhenxun.configs.utils import BaseBlock, Command, PluginExtraData, RegisterConfig from zhenxun.services.log import logger +from zhenxun.utils.decorator.shop import NotMeetUseConditionsException from zhenxun.utils.depends import UserName from zhenxun.utils.enum import BlockType, PluginType from zhenxun.utils.exception import GoodsNotFound @@ -202,6 +203,12 @@ async def _( await MessageUtils.build_message( f"没有找到道具 {name.result} 或道具数量不足..." ).send(reply_to=True) + except NotMeetUseConditionsException as e: + if info := e.get_info(): + await MessageUtils.build_message(info).finish() # type: ignore + await MessageUtils.build_message( + f"使用道具 {name.result} 的条件不满足..." + ).send(reply_to=True) @_matcher.assign("gold-list") diff --git a/zhenxun/builtin_plugins/shop/_data_source.py b/zhenxun/builtin_plugins/shop/_data_source.py index b5d693b4..c4343bbb 100644 --- a/zhenxun/builtin_plugins/shop/_data_source.py +++ b/zhenxun/builtin_plugins/shop/_data_source.py @@ -194,7 +194,6 @@ class ShopManage: "num": num, "text": text, "goods_name": goods.name, - "message": message, } @classmethod @@ -203,8 +202,9 @@ class ShopManage: args: MappingProxyType, param: ShopParam, session: Uninfo, + message: UniMsg, **kwargs, - ) -> list[Any]: + ) -> dict: """解析参数 参数: @@ -212,31 +212,30 @@ class ShopManage: param: ShopParam 返回: - list[Any]: 参数 + dict: 参数 """ - param_list = [] _bot = param.bot param.bot = None - param_json = {**param.to_dict(), **param.extra_data} - param_json["bot"] = _bot - for par in args.keys(): - if par in ["shop_param"]: - param_list.append(param) - elif par in ["session"]: - param_list.append(session) - elif par in ["message"]: - param_list.append(kwargs.get("message")) - elif par not in ["args", "kwargs"]: - param_list.append(param_json.get(par)) - if kwargs.get(par) is not None: - del kwargs[par] - return param_list + param_json = { + "bot": _bot, + "kwargs": kwargs, + **param.to_dict(), + **param.extra_data, + "session": session, + "message": message, + } + for key in list(param_json.keys()): + if key not in args: + del param_json[key] + return param_json @classmethod async def run_before_after( cls, goods: Goods, param: ShopParam, + session: Uninfo, + message: UniMsg, run_type: Literal["after", "before"], **kwargs, ): @@ -250,16 +249,19 @@ class ShopManage: fun_list = goods.before_handle if run_type == "before" else goods.after_handle if fun_list: for func in fun_list: - args = inspect.signature(func).parameters - if args and next(iter(args.keys())) != "kwargs": + if args := inspect.signature(func).parameters: if asyncio.iscoroutinefunction(func): - await func(*cls.__parse_args(args, param, **kwargs)) + await func( + **cls.__parse_args(args, param, session, message, **kwargs) + ) else: - func(*cls.__parse_args(args, param, **kwargs)) + func( + **cls.__parse_args(args, param, session, message, **kwargs) + ) elif asyncio.iscoroutinefunction(func): - await func(**kwargs) + await func() else: - func(**kwargs) + func() @classmethod async def __run( @@ -267,6 +269,7 @@ class ShopManage: goods: Goods, param: ShopParam, session: Uninfo, + message: UniMsg, **kwargs, ) -> str | UniMessage | None: """运行道具函数 @@ -280,18 +283,20 @@ class ShopManage: """ args = inspect.signature(goods.func).parameters # type: ignore if goods.func: - if args and next(iter(args.keys())) != "kwargs": + if args: return ( - await goods.func(*cls.__parse_args(args, param, session, **kwargs)) + await goods.func( + **cls.__parse_args(args, param, session, message, **kwargs) + ) if asyncio.iscoroutinefunction(goods.func) - else goods.func(*cls.__parse_args(args, param, session, **kwargs)) + else goods.func( + **cls.__parse_args(args, param, session, message, **kwargs) + ) ) if asyncio.iscoroutinefunction(goods.func): - return await goods.func( - **kwargs, - ) + return await goods.func() else: - return goods.func(**kwargs) + return goods.func() @classmethod async def use( @@ -339,12 +344,12 @@ class ShopManage: ) if num > param.max_num_limit: return f"{goods_info.goods_name} 单次使用最大数量为{param.max_num_limit}..." - await cls.run_before_after(goods, param, "before", **kwargs) - result = await cls.__run(goods, param, session, **kwargs) + await cls.run_before_after(goods, param, session, message, "before", **kwargs) + result = await cls.__run(goods, param, session, message, **kwargs) await UserConsole.use_props( session.user.id, goods_info.uuid, num, PlatformUtils.get_platform(session) ) - await cls.run_before_after(goods, param, "after", **kwargs) + await cls.run_before_after(goods, param, session, message, "after", **kwargs) if not result and param.send_success_msg: result = f"使用道具 {goods.name} {num} 次成功!" return result diff --git a/zhenxun/configs/utils/__init__.py b/zhenxun/configs/utils/__init__.py index 72b4af6c..03bc7331 100644 --- a/zhenxun/configs/utils/__init__.py +++ b/zhenxun/configs/utils/__init__.py @@ -170,6 +170,40 @@ class PluginSetting(BaseModel): """调用插件好感度限制""" +class AICallableProperties(BaseModel): + type: str + """参数类型""" + description: str + """参数描述""" + enums: list[str] | None = None + """参数枚举""" + + +class AICallableParam(BaseModel): + type: str + """类型""" + properties: dict[str, AICallableProperties] + """参数列表""" + required: list[str] + """必要参数""" + + +class AICallableTag(BaseModel): + name: str + """工具名称""" + parameters: AICallableParam | None = None + """工具参数""" + description: str + """工具描述""" + func: Callable | None = None + """工具函数""" + + def to_dict(self): + result = model_dump(self) + del result["func"] + return result + + class SchedulerModel(BaseModel): trigger: Literal["date", "interval", "cron"] """trigger""" @@ -249,6 +283,8 @@ class PluginExtraData(BaseModel): """常用sql""" is_show: bool = True """是否显示在菜单中""" + smart_tools: list[AICallableTag] | None = None + """智能模式函数工具集""" def to_dict(self, **kwargs): return model_dump(self, **kwargs) diff --git a/zhenxun/services/__init__.py b/zhenxun/services/__init__.py index 980a7277..14ae227f 100644 --- a/zhenxun/services/__init__.py +++ b/zhenxun/services/__init__.py @@ -5,3 +5,4 @@ require("nonebot_plugin_alconna") require("nonebot_plugin_session") require("nonebot_plugin_htmlrender") require("nonebot_plugin_uninfo") +require("nonebot_plugin_waiter") diff --git a/zhenxun/utils/decorator/retry.py b/zhenxun/utils/decorator/retry.py index 2a6cc757..ddc55584 100644 --- a/zhenxun/utils/decorator/retry.py +++ b/zhenxun/utils/decorator/retry.py @@ -1,16 +1,24 @@ +from anyio import EndOfStream from httpx import ConnectError, HTTPStatusError, TimeoutException from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed class Retry: @staticmethod - def api(): + def api( + retry_count: int = 3, wait: int = 1, exception: tuple[type[Exception], ...] = () + ): """接口调用重试""" + base_exceptions = ( + TimeoutException, + ConnectError, + HTTPStatusError, + EndOfStream, + *exception, + ) return retry( reraise=True, - stop=stop_after_attempt(3), - wait=wait_fixed(1), - retry=retry_if_exception_type( - (TimeoutException, ConnectError, HTTPStatusError) - ), + stop=stop_after_attempt(retry_count), + wait=wait_fixed(wait), + retry=retry_if_exception_type(base_exceptions), )