新增插件智能模式适配 (#1850)

* 新增插件智能模式适配

* 🚨 auto fix by pre-commit hooks

* 更改类名,命名更清晰

* 🎨 添加模块化参数

* 🎨  AI模块化修改

* 🩹  道具调用修复

* 🩹 修复商品使用前检测

*  retry增加参数适配

*   修改道具使用函数参数传递

*  捕获道具无法使用异常

* 🐛 添加依赖require

* 🐛  修复插件使用问题

---------

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>
This commit is contained in:
ChthollyWn 2025-02-24 09:28:53 +08:00 committed by GitHub
parent 78df9ed086
commit a6ddb726d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 97 additions and 40 deletions

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -5,3 +5,4 @@ require("nonebot_plugin_alconna")
require("nonebot_plugin_session")
require("nonebot_plugin_htmlrender")
require("nonebot_plugin_uninfo")
require("nonebot_plugin_waiter")

View File

@ -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),
)