From 8aee04829be1e963f18ac11d10aad90ca41b0998 Mon Sep 17 00:00:00 2001 From: RF-Tar-Railt <3165388245@qq.com> Date: Mon, 3 Jul 2023 21:02:03 +0800 Subject: [PATCH] :sparkles: version 0.9.0 add `funcommand` --- README.md | 10 ++++++ docs.md | 13 ++++++++ pdm.lock | 42 ++++++++++++++++--------- pyproject.toml | 8 ++--- src/nonebot_plugin_alconna/__init__.py | 3 +- src/nonebot_plugin_alconna/matcher.py | 43 ++++++++++++++++++++++++++ src/nonebot_plugin_alconna/matcher.pyi | 11 ++++++- src/nonebot_plugin_alconna/typings.py | 3 +- src/test/.env | 4 --- src/test/.env.dev | 1 - src/test/.env.prod | 5 +++ src/test/bot.py | 4 +-- src/test/plugins/demo.py | 23 ++++++++------ src/test/pyproject.toml | 6 ++-- 14 files changed, 135 insertions(+), 41 deletions(-) delete mode 100644 src/test/.env delete mode 100644 src/test/.env.dev diff --git a/README.md b/README.md index 6b4ffcb..7329994 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,17 @@ def on_alconna( | [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | adapters.bilibili | +### 便捷装饰器 +`funcommand` 装饰器用于将一个接受任意参数,返回 `str` 或 `Message` 或 `MessageSegment` 的函数转换为命令响应器。 + +```python +from nonebot_plugin_alconna import funcommand + +@funcommand() +async def echo(msg: str): + return msg +``` ## 体验 diff --git a/docs.md b/docs.md index 749cd34..cf4d721 100644 --- a/docs.md +++ b/docs.md @@ -173,6 +173,19 @@ async def install(arp: CommandResult = AlconnaResult()): ... ``` +### 便捷装饰器 + +本插件提供了一个 `funcommand` 装饰器, 其用于将一个接受任意参数, +返回 `str` 或 `Message` 或 `MessageSegment` 的函数转换为命令响应器。 + +```python +from nonebot_plugin_alconna import funcommand + +@funcommand() +async def echo(msg: str): + return msg +``` + ## MessageSegment 标注 本插件提供了一系列便捷的 `MessageSegment` 标注,可用于匹配消息中除 text 外的其他 `MessageSegment`,也可用于快速创建 `MessageSegment`。 diff --git a/pdm.lock b/pdm.lock index 4156879..3b9791e 100644 --- a/pdm.lock +++ b/pdm.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "arclet-alconna" -version = "1.7.7" +version = "1.7.10" requires_python = ">=3.8" summary = "A High-performance, Generality, Humane Command Line Arguments Parser Library." dependencies = [ @@ -30,11 +30,11 @@ dependencies = [ [[package]] name = "arclet-alconna-tools" -version = "0.6.1" +version = "0.6.3" requires_python = ">=3.8" summary = "Builtin Tools for Alconna" dependencies = [ - "arclet-alconna>=1.7.7", + "arclet-alconna>=1.7.10", "nepattern<0.6.0,>=0.5.8", ] @@ -244,7 +244,7 @@ dependencies = [ [[package]] name = "nonebot-adapter-telegram" -version = "0.1.0b13" +version = "0.1.0b14" requires_python = ">=3.8,<4.0" summary = "Telegram Adapter for NoneBot2" dependencies = [ @@ -266,6 +266,20 @@ dependencies = [ "yarl<2.0.0,>=1.7.2", ] +[[package]] +name = "nonebot2" +version = "2.0.0" +requires_python = ">=3.8,<4.0" +summary = "An asynchronous python bot framework." +dependencies = [ + "loguru<1.0.0,>=0.6.0", + "pydantic[dotenv]<2.0.0,>=1.10.0", + "pygtrie<3.0.0,>=2.4.1", + "tomli<3.0.0,>=2.0.1; python_version < \"3.11\"", + "typing-extensions<5.0.0,>=4.0.0", + "yarl<2.0.0,>=1.7.2", +] + [[package]] name = "pycryptodome" version = "3.16.0" @@ -412,7 +426,7 @@ summary = "Backport of pathlib-compatible object wrapper for zip files" lock_version = "4.2" cross_platform = true groups = ["default", "dev"] -content_hash = "sha256:8671a704ac7e3328d8f9a12e77c26cde34a85a96e8bb0bf726369cbd2f2c3d0b" +content_hash = "sha256:a1e58af988998c39b9f465d4b09d42f645dc3474e3af91de2b84551fab1671b2" [metadata.files] "aio-mc-rcon 3.2.0" = [ @@ -423,13 +437,13 @@ content_hash = "sha256:8671a704ac7e3328d8f9a12e77c26cde34a85a96e8bb0bf726369cbd2 {url = "https://files.pythonhosted.org/packages/77/2b/b4c0b7a3f3d61adb1a1e0b78f90a94e2b6162a043880704b7437ef297cad/anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, {url = "https://files.pythonhosted.org/packages/8b/94/6928d4345f2bc1beecbff03325cad43d320717f51ab74ab5a571324f4f5a/anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, ] -"arclet-alconna 1.7.7" = [ - {url = "https://files.pythonhosted.org/packages/87/f3/ee4aa593a5e12ade6ebe033ca67a66ffa35a57f93e4efd6d460e29129770/arclet_alconna-1.7.7-py3-none-any.whl", hash = "sha256:46f39cacdf85993ca86e88842b697880a6e4662d40b2c05ccb2909ac39cb0320"}, - {url = "https://files.pythonhosted.org/packages/e0/ee/4d384f1111db814cd91084969f5761e98a6971d33cb6bb6b89995d1bea40/arclet-alconna-1.7.7.tar.gz", hash = "sha256:86c4a6297b005f2ca817e03c02430c2b7da241c856645db56b61a91baa8b14ff"}, +"arclet-alconna 1.7.10" = [ + {url = "https://files.pythonhosted.org/packages/0b/e4/6c9d62300864934a9fbe2a44d9a64dc69b646c3f3f49dc6b65327f9c1a80/arclet_alconna-1.7.10.tar.gz", hash = "sha256:b0c509aac7875312eaacf3f5feed70163092e9b46e5341554f895acbd58fb7b4"}, + {url = "https://files.pythonhosted.org/packages/6f/0a/4bc7592a6a3a95eeb3c79a3be25e3b35c0244bf0f70324d86545d8f5a4ca/arclet_alconna-1.7.10-py3-none-any.whl", hash = "sha256:1f0d5817964574921c8d08c837436eca2ebf7ad71e8c7f1ff95a2d5cdffbe558"}, ] -"arclet-alconna-tools 0.6.1" = [ - {url = "https://files.pythonhosted.org/packages/94/fd/f576afeab4c890fb1db9311059000406cad140034e5fe1cdfb2195fa2321/arclet-alconna-tools-0.6.1.tar.gz", hash = "sha256:7808b638dd5264c4adc98f35a8b4021196c09f4ea95cd036261a2b7d079f7bde"}, - {url = "https://files.pythonhosted.org/packages/a0/e9/f2ff6c6e20623da3c675aa1f242108737a83604030001ecdc5707aa95cd7/arclet_alconna_tools-0.6.1-py3-none-any.whl", hash = "sha256:cc46fe361c15f2728c189b5d39db8178ea5e138d5084e3fd705762d1ac722c1b"}, +"arclet-alconna-tools 0.6.3" = [ + {url = "https://files.pythonhosted.org/packages/84/d1/974e51c61abb9d6b8805d8a929abe80da749301f8ff42c53fa0eddaa5d8e/arclet-alconna-tools-0.6.3.tar.gz", hash = "sha256:efb8944a720e648f5dd91001a61466dd1f2ca2aac272c8fa61cfbad9c5b10f17"}, + {url = "https://files.pythonhosted.org/packages/ae/f6/c6416a779399892f94eaf64b1b7710148ec3d23fecda43030b3bdf211816/arclet_alconna_tools-0.6.3-py3-none-any.whl", hash = "sha256:7462fec995c0644186c23473dd6b57af6dde2af4a7fdd8f2ecfe4c842039a355"}, ] "cashews 6.1.0" = [ {url = "https://files.pythonhosted.org/packages/75/95/31bfab07df941d902edad8b3628500147aeef1e1cc4c3a3a18f524cf26af/cashews-6.1.0-py3-none-any.whl", hash = "sha256:bd620e5fdb947949aca0f107f0275a48d46026c747695186d2507f89a48d1af9"}, @@ -649,9 +663,9 @@ content_hash = "sha256:8671a704ac7e3328d8f9a12e77c26cde34a85a96e8bb0bf726369cbd2 {url = "https://files.pythonhosted.org/packages/65/7b/fe317d0f59be7d149b4d96e3fbe4c69b9c1c35a9ff313a46027140c3c1b5/nonebot_adapter_qqguild-0.2.3-py3-none-any.whl", hash = "sha256:63c97b5d96f4a75d1dcf1d62b5374a6ce6163ef52550f2c8a26dd213087b8828"}, {url = "https://files.pythonhosted.org/packages/6a/34/8dc5351a724e57530bf22264bdee877a50f3fb7f3a90b51fdd9b46d9138b/nonebot_adapter_qqguild-0.2.3.tar.gz", hash = "sha256:fe0e70abd3e94fc7d59a782f0641571780a759a3a289f5448a1d298a342be34a"}, ] -"nonebot-adapter-telegram 0.1.0b13" = [ - {url = "https://files.pythonhosted.org/packages/1f/67/e678aed9ecdb95bc0de4b4b65f75fcb8fb9f0c08cdf9954071ed909ad0e3/nonebot-adapter-telegram-0.1.0b13.tar.gz", hash = "sha256:45d679a0d08a71e1b2f9758c02b545b1e28e711331a963a83574c5297d20ce2a"}, - {url = "https://files.pythonhosted.org/packages/4e/f3/0cfecc98994f2df6573544f31fcddccfa1f19a62121b782cb7dfbc7bf076/nonebot_adapter_telegram-0.1.0b13-py3-none-any.whl", hash = "sha256:d709d0181601aaa32dff4fda26fb9853a12344fcaf5433dbc0e5ea694886493d"}, +"nonebot-adapter-telegram 0.1.0b14" = [ + {url = "https://files.pythonhosted.org/packages/9f/ec/83e1fdcbc22798309978498ce76a22fad706cd192691dc7149f2f9f17da3/nonebot-adapter-telegram-0.1.0b14.tar.gz", hash = "sha256:9e6f7b7b1b04eaac7af7e3fb496875d988be84a8a22bd67afde4e5b08be0fcbc"}, + {url = "https://files.pythonhosted.org/packages/cf/9e/d478725be97d983963228e3c0a96721218a8c24488545f6604e8e85944dc/nonebot_adapter_telegram-0.1.0b14-py3-none-any.whl", hash = "sha256:2b8a1f8ee0833994b00d48ffb57833fc52fe43290b0e100cf9f81e01412872b1"}, ] "nonebot2 2.0.0" = [ {url = "https://files.pythonhosted.org/packages/69/60/2c0d414ff1dd015848f28a7f63181d45838a91d406af8f8bbc595185561b/nonebot2-2.0.0.tar.gz", hash = "sha256:144c175ce100c3300d53475fc47b7a9f0a6545861ff12bdc8a1442ab12d430df"}, diff --git a/pyproject.toml b/pyproject.toml index 03cb85d..0faa78d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,14 @@ [project] name = "nonebot-plugin-alconna" -version = "0.8.4" +version = "0.9.0" description = "Alconna Adapter for Nonebot" authors = [ {name = "RF-Tar-Railt", email = "rf_tar_railt@qq.com"}, ] dependencies = [ "nonebot2>=2.0.0rc4", - "arclet-alconna<2.0.0, >=1.7.7", - "arclet-alconna-tools<0.7.0, >=0.6.1", + "arclet-alconna<2.0.0, >=1.7.10", + "arclet-alconna-tools<0.7.0, >=0.6.3", ] requires-python = ">=3.8" readme = "README.md" @@ -30,7 +30,7 @@ build-backend = "pdm.pep517.api" [tool.pdm] [tool.pdm.dev-dependencies] dev = [ - "nonebot2>=2.0.0", + "nonebot2[fastapi,httpx,websockets]>=2.0.0", "fix-future-annotations>=0.5.0", "nonebot-adapter-onebot>=2.2.3", "nonebot-adapter-feishu>=2.0.0b8", diff --git a/src/nonebot_plugin_alconna/__init__.py b/src/nonebot_plugin_alconna/__init__.py index f5861f7..f311d69 100644 --- a/src/nonebot_plugin_alconna/__init__.py +++ b/src/nonebot_plugin_alconna/__init__.py @@ -4,6 +4,7 @@ from .argv import MessageArgv as MessageArgv from .consts import ALCONNA_RESULT as ALCONNA_RESULT from .matcher import on_alconna as on_alconna +from .matcher import funcommand as funcommand from .model import CommandResult as CommandResult from .model import Match as Match from .model import Query as Query @@ -22,7 +23,7 @@ from .rule import set_output_converter as set_output_converter from .config import Config -__version__ = "0.8.4" +__version__ = "0.9.0" _meta_source = { "name": "Alconna 插件", diff --git a/src/nonebot_plugin_alconna/matcher.py b/src/nonebot_plugin_alconna/matcher.py index fb0c231..92e1bd8 100644 --- a/src/nonebot_plugin_alconna/matcher.py +++ b/src/nonebot_plugin_alconna/matcher.py @@ -1,11 +1,16 @@ from __future__ import annotations +from typing import Callable + from arclet.alconna import Alconna, command_manager from arclet.alconna.tools import AlconnaFormat +from arclet.alconna.tools.construct import FuncMounter +from tarina import is_awaitable from nonebot.matcher import Matcher from nonebot.plugin.on import on_message from nonebot.rule import Rule from nonebot.typing import T_RuleChecker +from nonebot.internal.adapter import Bot, Event, Message, MessageSegment from .model import CompConfig from .rule import alconna @@ -62,3 +67,41 @@ def on_alconna( **kwargs, _depth=_depth + 1 # type: ignore ) + + +def funcommand( + name: str | None = None, + prefixes: list[str] | None = None, + description: str | None = None, +): + _config = {"raise_exception": False} + if name: + _config["name"] = name + if prefixes: + _config["prefixes"] = prefixes + if description: + _config["description"] = description + def wrapper(func: Callable) -> type[Matcher]: + alc = FuncMounter(func, _config) # type: ignore + + async def handle(bot: Bot, event: Event): + msg = getattr(event, "original_message", event.get_message()) + try: + arp, res = alc.exec(msg) + except Exception as e: + if _config["raise_exception"]: + raise e + await bot.send(event, str(e)) + return + if arp.matched: + if is_awaitable(res): + res = await res + if isinstance(res, (str, Message, MessageSegment)): + await bot.send(event, res) + + matcher = on_alconna(alc) + matcher.handle()(handle) + + return matcher + + return wrapper diff --git a/src/nonebot_plugin_alconna/matcher.pyi b/src/nonebot_plugin_alconna/matcher.pyi index 0b55ee5..a361174 100644 --- a/src/nonebot_plugin_alconna/matcher.pyi +++ b/src/nonebot_plugin_alconna/matcher.pyi @@ -1,6 +1,7 @@ from __future__ import annotations from datetime import datetime, timedelta +from typing import Callable from arclet.alconna import Alconna from nonebot.dependencies import Dependent @@ -9,7 +10,7 @@ from nonebot.permission import Permission from nonebot.rule import Rule from nonebot.typing import T_Handler, T_PermissionChecker, T_RuleChecker, T_State from nonebot_plugin_alconna.model import CompConfig -from nonebot_plugin_alconna.typings import TConvert +from nonebot_plugin_alconna.typings import TConvert, MReturn def on_alconna( command: Alconna | str, @@ -28,3 +29,11 @@ def on_alconna( block: bool = ..., state: T_State | None = ..., ) -> type[Matcher]: ... + + +def funcommand( + name: str | None = None, + prefixes: list[str] | None = None, + description: str | None = None, +) -> Callable[[Callable[..., MReturn]], type[Matcher]]: + ... diff --git a/src/nonebot_plugin_alconna/typings.py b/src/nonebot_plugin_alconna/typings.py index 622ca3a..f19f707 100644 --- a/src/nonebot_plugin_alconna/typings.py +++ b/src/nonebot_plugin_alconna/typings.py @@ -9,6 +9,7 @@ T = TypeVar("T") TMS = TypeVar("TMS", bound=MessageSegment) +TCallable = TypeVar("TCallable", bound=Callable[..., Any]) P = ParamSpec("P") @@ -47,7 +48,7 @@ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> TMS: OutputType = Literal["help", "shortcut", "completion"] TConvert: TypeAlias = Callable[[OutputType, str], Union[Message, Awaitable[Message]]] - +MReturn: TypeAlias = Union[Union[str, Message, MessageSegment], Awaitable[Union[str, Message, MessageSegment]]] def _isinstance(seg: MessageSegment, mapping: dict[str, Callable[[MessageSegment], Any]]): if (key := seg.type) in mapping and (res := mapping[key](seg)): diff --git a/src/test/.env b/src/test/.env deleted file mode 100644 index d3ef620..0000000 --- a/src/test/.env +++ /dev/null @@ -1,4 +0,0 @@ -ENVIRONMENT=dev -DRIVER=~none -ALCONNA_AUTO_SEND_OUTPUT=true -ALCONNA_USE_COMMAND_START=false \ No newline at end of file diff --git a/src/test/.env.dev b/src/test/.env.dev deleted file mode 100644 index 2a1b856..0000000 --- a/src/test/.env.dev +++ /dev/null @@ -1 +0,0 @@ -LOG_LEVEL=DEBUG diff --git a/src/test/.env.prod b/src/test/.env.prod index e69de29..e0d8428 100644 --- a/src/test/.env.prod +++ b/src/test/.env.prod @@ -0,0 +1,5 @@ +DRIVER=~fastapi+~httpx+~websockets +HOST=127.0.0.1 +PORT=9555 +ALCONNA_AUTO_SEND_OUTPUT=true +ALCONNA_USE_COMMAND_START=false \ No newline at end of file diff --git a/src/test/bot.py b/src/test/bot.py index 81cece3..3c644f6 100644 --- a/src/test/bot.py +++ b/src/test/bot.py @@ -1,9 +1,9 @@ import nonebot -from nonebot.adapters.console import Adapter as ConsoleAdapter +from nonebot.adapters.onebot.v12 import Adapter as ONEBOT_V12Adapter nonebot.init() driver = nonebot.get_driver() -driver.register_adapter(ConsoleAdapter) +driver.register_adapter(ONEBOT_V12Adapter) # nonebot.require("nonebot_plugin_alconna") nonebot.load_plugin("plugins.demo") diff --git a/src/test/plugins/demo.py b/src/test/plugins/demo.py index 24e0a37..df49ce7 100644 --- a/src/test/plugins/demo.py +++ b/src/test/plugins/demo.py @@ -1,21 +1,19 @@ # 应与使用的 adapter 对应 # 不加也可以,做了兼容 -import nonebot_plugin_alconna.adapters.console # noqa -from arclet.alconna import Alconna, Args, Arparma, Option, Subcommand, command_manager, namespace, Duplication, SubcommandStub, Empty -from arclet.alconna.tools import MarkdownTextFormatter +import nonebot_plugin_alconna.adapters.onebot12 # noqa: F401 +from arclet.alconna import Alconna, Args, Arparma, Option, Subcommand, command_manager, namespace, Duplication, SubcommandStub from importlib_metadata import distributions -from nonebot.adapters.console.message import Message, MessageSegment +from nonebot.adapters.onebot.v12.message import Message, MessageSegment from nonebot_plugin_alconna import ( AlconnaMatches, on_alconna, set_output_converter, AlconnaDuplication, - Check, assign + Check, assign, funcommand ) from tarina import lang -set_output_converter(lambda t, x: Message([MessageSegment.markdown(x)])) +set_output_converter(lambda t, x: Message([MessageSegment.text(x)])) with namespace("nbtest") as ns: ns.headers = ["/"] - ns.formatter_type = MarkdownTextFormatter ns.builtin_option_name["help"] = {"-h", "帮助", "--help"} help_cmd = on_alconna(Alconna("help")) @@ -33,7 +31,7 @@ # auto_send already set in .env pipcmd = on_alconna(pip, comp_config={'timeout': 10}, block=True) # , auto_send_output=True) - ali = on_alconna(Alconna(["/"], "一言"), aliases={"hitokoto"}, skip_for_unmatch=False) + ali = on_alconna(Alconna(["/"], "一言"), aliases={"hitokoto"}, skip_for_unmatch=True) i18n = on_alconna(Alconna("lang", Args["lang", ["zh_CN", "en_US"]])) class PipResult(Duplication): @@ -55,7 +53,7 @@ def get_dist_map() -> dict: @help_cmd.handle() async def _help(arp: Arparma = AlconnaMatches()): - await help_cmd.send(MessageSegment.markdown(command_manager.all_command_help())) + await help_cmd.send(MessageSegment.text(command_manager.all_command_help())) @i18n.handle() @@ -70,7 +68,7 @@ async def _i18n(arp: Arparma = AlconnaMatches()): @pipcmd.handle([Check(assign("list"))]) async def ll(): md = "\n".join([f"- {k} {v}" for k, v in get_dist_map().items()]) - await pipcmd.send(MessageSegment.markdown(md)) + await pipcmd.send(MessageSegment.text(md)) @pipcmd.handle([Check(assign("install.pak"))]) @@ -89,3 +87,8 @@ async def yiyan(res: Arparma = AlconnaMatches()): await ali.send("WIP...") # else: # await ali.send(f"[hitokoto] Unmatched: {res}") + +@funcommand() +def add(a: float, b: float): + """加法测试""" + return f"{a} + {b} = {a + b}" diff --git a/src/test/pyproject.toml b/src/test/pyproject.toml index 9b97f6a..71b2a9b 100644 --- a/src/test/pyproject.toml +++ b/src/test/pyproject.toml @@ -7,8 +7,8 @@ requires-python = ">=3.8, <4.0" [tool.nonebot] adapters = [ - { name = "Console", module_name = "nonebot.adapters.console" }, + { name = "OneBot V12", module_name = "nonebot.adapters.onebot.v12" } ] -plugins = [] -plugin_dirs = ["src/plugins"] +plugins = ["nonebot_plugin_alconna"] +plugin_dirs = [] builtin_plugins = []