🔖 Release 2.4.3
Some checks failed
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.10) (push) Failing after 16s
Ruff Lint / Ruff Lint (push) Successful in 33s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.12) (push) Failing after 41s
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.9) (push) Failing after 1m13s
Pyright Lint / Pyright Lint (pydantic-v1) (push) Successful in 1m20s
Pyright Lint / Pyright Lint (pydantic-v2) (push) Failing after 1m39s
Site Deploy / publish (push) Failing after 1m45s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.9) (push) Failing after 2m3s
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.11) (push) Failing after 2m45s
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.12) (push) Failing after 3m45s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.11) (push) Failing after 3m52s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.10) (push) Failing after 3m56s
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.9) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.12) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.9) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.9) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.12) (push) Has been cancelled
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.9) (push) Has been cancelled

This commit is contained in:
noneflow[bot]
2025-08-07 10:13:49 +00:00
parent ec965fc705
commit 2b9f8c15f2
104 changed files with 21227 additions and 2 deletions

View File

@ -0,0 +1,162 @@
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
# Alconna 插件
[`nonebot-plugin-alconna`](https://github.com/nonebot/plugin-alconna) 是一类极大地提升了 NoneBot 开发体验的插件。
该插件可分为三个部分:
- 增强的命令解析: 基于 [Alconna](https://github.com/ArcletProject/Alconna), 提供一类新的事件响应器辅助函数 `on_alconna`. 相比 `on_command`, `on_shell`, `on_regex` 等函数,`on_alconna` 提供了更强大的命令解析能力与诸多特性。
- 通用消息组件: 实现了跨平台接收、发送、撤回、编辑、表态消息的功能。
- `UniMessage` 通用消息模型,支持各适配器下的消息转换和导出,发送。
- `Text`, `Image`, `At` 等通用消息段模型,既与 `UniMessage` 配合使用,又能用于 `Alconna` 的命令解析。
- `message_recall`, `message_edit`, `message_reaction` 等功能函数。
- `Target` 通用消息目标模型,并通过该模型进行主动消息发送。
- `UniMsg`, `MsgId`, `MsgTarget`, `at_in`, `at_me` 等提供给 nonebot 使用的依赖注入和 `Rule`。
- 内置功能插件:基于上述部分实现的内置功能插件。
- `echo`: 通过 `on_alconna` 实现的 echo 插件,支持回显回复消息。
- `help`: 列出所有 `on_alconna` 事件响应器的帮助信息或其对应的插件信息。
- `lang`: 切换 `Alconna` 使用的语言
- `switch`: 禁用/启用某个指令
- `with`: 针对具有多个子命令的指令,通过 `with` 在当前会话中载入命令头以节省输入。
以最新版本为例 (v0.59), 本插件已支持 NoneBot 生态中几乎所有的适配器, 包括:
| 协议名称 | 路径 |
| ------------------------------------------------------------------- | ------------------------------------ |
| [OneBot 协议](https://onebot.dev/) | adapters.onebot11, adapters.onebot12 |
| [Telegram](https://core.telegram.org/bots/api) | adapters.telegram |
| [飞书](https://open.feishu.cn/document/home/index) | adapters.feishu |
| [GitHub](https://docs.github.com/en/developers/apps) | adapters.github |
| [QQ bot](https://github.com/nonebot/adapter-qq) | adapters.qq |
| [钉钉](https://open.dingtalk.com/document/) | adapters.ding |
| [Console](https://github.com/nonebot/adapter-console) | adapters.console |
| [开黑啦](https://developer.kookapp.cn/) | adapters.kook |
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | adapters.mirai |
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | adapters.ntchat |
| [MineCraft](https://github.com/17TheWord/nonebot-adapter-minecraft) | adapters.minecraft |
| [Walle-Q](https://github.com/onebot-walle/nonebot_adapter_walleq) | adapters.onebot12 |
| [Discord](https://github.com/nonebot/adapter-discord) | adapters.discord |
| [Red 协议](https://github.com/nonebot/adapter-red) | adapters.red |
| [Satori](https://github.com/nonebot/adapter-satori) | adapters.satori |
| [Dodo IM](https://github.com/nonebot/adapter-dodo) | adapters.dodo |
| [Kritor](https://github.com/nonebot/adapter-kritor) | adapters.kritor |
| [Tailchat](https://github.com/eya46/nonebot-adapter-tailchat) | adapters.tailchat |
| [Mail](https://github.com/mobyw/nonebot-adapter-mail) | adapters.mail |
| [微信公众号](https://github.com/YangRucheng/nonebot-adapter-wxmp) | adapters.wxmp |
| [黑盒语音](https://github.com/lclbm/adapter-heybox) | adapters.heybox |
| [Milky](https://github.com/nonebot/adapter-milky) | adapters.milky |
| [EFChat](https://github.com/molanp/nonebot_adapter_efchat) | adapters.efchat |
## 安装插件
在使用前请先安装 `nonebot-plugin-alconna` 插件至项目环境中,可参考[获取商店插件](../../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如:
在**项目目录**下执行以下命令:
<Tabs groupId="install">
<TabItem value="cli" label="使用 nb-cli">
```shell
nb plugin install nonebot-plugin-alconna
```
</TabItem>
<TabItem value="pip" label="使用 pip">
```shell
pip install nonebot-plugin-alconna
```
</TabItem>
<TabItem value="pdm" label="使用 pdm">
```shell
pdm add nonebot-plugin-alconna
```
</TabItem>
</Tabs>
## 导入插件
由于 `nonebot-plugin-alconna` 作为插件,因此需要在使用前对其进行**加载**。使用 `require` 方法可轻松完成这一过程,可参考 [跨插件访问](../../advanced/requiring.md) 一节进行了解。
```python
from nonebot import require
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import ...
```
## 使用插件
在前面的[深入指南](../../appendices/session-control.mdx)中,我们已经得到了一个天气插件。
现在我们将使用 `Alconna` 来改写这个插件。
<details>
<summary>插件示例</summary>
```python title=weather/__init__.py
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.matcher import Matcher
from nonebot.adapters import Message
from nonebot.params import CommandArg, ArgPlainText
weather = on_command("天气", rule=to_me(), aliases={"weather", "天气预报"})
@weather.handle()
async def handle_function(matcher: Matcher, args: Message = CommandArg()):
if args.extract_plain_text():
matcher.set_arg("location", args)
@weather.got("location", prompt="请输入地名")
async def got_location(location: str = ArgPlainText()):
if location not in ["北京", "上海", "广州", "深圳"]:
await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
await weather.finish(f"今天{location}的天气是...")
```
</details>
```python {5-9,13-15,17-18}
from nonebot.rule import to_me
from arclet.alconna import Alconna, Args
from nonebot_plugin_alconna import Match, on_alconna
weather = on_alconna(
Alconna("天气", Args["location?", str]),
aliases={"weather", "天气预报"},
rule=to_me(),
)
@weather.handle()
async def handle_function(location: Match[str]):
if location.available:
weather.set_path_arg("location", location.result)
@weather.got_path("location", prompt="请输入地名")
async def got_location(location: str):
if location not in ["北京", "上海", "广州", "深圳"]:
await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
await weather.finish(f"今天{location}的天气是...")
```
在上面的代码中,我们使用 `Alconna` 来解析命令,`on_alconna` 用来创建响应器,使用 `Match` 来获取解析结果。
关于更多 `Alconna` 的使用方法,可参考 [Alconna 文档](https://arclet.top/tutorial/alconna)
或阅读 [Alconna 基本介绍](./command.md) 一节。
关于更多 `on_alconna` 的使用方法,可参考 [插件文档](https://github.com/nonebot/plugin-alconna/blob/master/docs.md)
或阅读 [响应规则的使用](./matcher.mdx) 一节。
## 交流与反馈
QQ 交流群: [🔗 链接](https://jq.qq.com/?_wv=1027&k=PUPOnCSH)
友链: [📚 文档](https://graiax.cn/guide/message_parser/alconna.html)

View File

@ -0,0 +1,4 @@
{
"label": "命令解析拓展",
"position": 6
}

View File

@ -0,0 +1,294 @@
---
sidebar_position: 7
description: 内置组件
---
import Messenger from "@site/src/components/Messenger";
# 内置组件
`nonebot_plugin_alconna` 插件提供了一系列内置组件以提升开发者和用户体验。
## 内置插件
类似于 Nonebot 本身提供的内置插件,`nonebot_plugin_alconna` 提供了多个内置插件。
### 加载
你可以用本插件的 `load_builtin_plugin(s)` 来加载它们:
```python
from nonebot_plugin_alconna import load_builtin_plugin, load_builtin_plugins
load_builtin_plugins("echo")
load_builtin_plugins("help", "with")
```
### 使用
#### echo
`echo` 插件能将用户发送的消息原样返回。
<Messenger
msgs={[
{ position: "right", msg: "/echo hello world!" },
{ position: "left", msg: "hello world!" },
{ position: "right", msg: "/echo [图片]" },
{ position: "left", msg: "[图片]" },
]}
/>
#### help
`help` 插件能列出所有 Alconna 指令。同时还能查询某个指令对应的插件信息。
<Messenger
msgs={[
{ position: "right", msg: "/帮助" },
{
position: "left",
msg: "# 当前可用的命令有:\n 【0】/echo : echo 指令\n 【1】/help : 显示所有命令帮助\n# 输入'命令名 -h|--help' 查看特定命令的语法",
},
{ position: "right", msg: "/help --plugin-info echo" },
{
position: "left",
msg: "插件名称: echo\n插件标识: nonebot_plugin_alconna:echo\n插件模块: nonebot-plugin-alconna\n插件版本: 0.57.2\n插件路径: nonebot_plugin_alconna.builtins.plugins.echo",
},
]}
/>
help 插件的帮助信息如下:
```
/help <query: str = -1>
## 注释
query: 选择某条命令的id或者名称查看具体帮助
显示所有命令帮助
用法:
可以使用 --hide 参数来显示隐藏命令,使用 -P 参数来显示命令所属插件名称
可用的子命令有:
* 是否列出命令所属命名空间
-N│--namespace│命名空间 [target: str]
## 注释
target: 指定的命名空间
该子命令内可用的选项有:
* 列出所有命名空间
--list
可用的选项有:
* 查看指定页数的命令帮助
--page <index: int>
* 查看命令所属插件的信息
-P│插件信息│--plugin-info
* 是否列出隐藏命令
隐藏│-H│--hide
```
#### lang
`lang` 插件能切换 i18n 的语言设置。
<Messenger
msgs={[
{ position: "right", msg: "/lang list" },
{
position: "left",
msg: "支持的语言列表:\n * en-US\n * zh-CN",
},
{ position: "right", msg: "/lang switch en-US" },
{ position: "left", msg: "Switch to 'en-US' successfully." },
]}
/>
lang 插件的帮助信息如下:
```
/lang
i18n配置相关功能
可用的选项有:
* 查看支持的语言列表
list [name: str]
* 切换语言
switch [locale: str]
```
其中 `list` 选项可以查找某一插件下的语言支持情况 (例如 `/lang list nonebot_plugin_alconna`)。
#### switch
`switch` 插件能用来启用/禁用某个命令,其使用方法与 `help` 类似。
<Messenger
msgs={[
{ position: "right", msg: "/disable" },
{
position: "left",
msg: "【0】/echo : echo 指令\n【1】/help : 显示所有命令帮助\n【2】/lang : i18n配置相关功能",
},
{ position: "right", msg: "/disable 0" },
{ position: "left", msg: "已禁用 /echo" },
{ position: "right", msg: "/echo 1234" },
{ position: "right", msg: "/enable echo" },
{ position: "left", msg: "已启用 /echo" },
{ position: "right", msg: "/echo 1234" },
{ position: "left", msg: "1234" },
]}
/>
#### with
`with` 插件能在当前会话中设置一个局部命令前缀,以便于有多个子命令的指令使用。
<Messenger
msgs={[
{ position: "right", msg: "/with" },
{
position: "left",
msg: "当前群组未设置前缀",
},
{ position: "right", msg: "/with lang" },
{ position: "left", msg: "设置前缀成功" },
{ position: "right", msg: "list" },
{
position: "left",
msg: "支持的语言列表:\n * en-US\n * zh-CN",
},
]}
/>
with 插件的帮助信息如下:
```
.with [name: str]
with 指令
用法:
设置局部命令前缀
可用的选项有:
* 设置可能的生效时间
--expire│expire <time: datetime>
* 取消当前前缀
unset│--unset
快捷命令:
'[.]局部前缀' => [.]with
```
### 配置
内置插件也有其配置项,并且均以 `NBP_ALC` 开头。
- `nbp_alc_echo_tome`: 是否让 `echo` 插件的消息经过 `to_me` 处理
- `nbp_alc_page_size`: `help` 与 `switch` 插件的共同配置项,表示每页显示的命令数量
- `nbp_alc_help_text`: `help` 指令的指令名,默认为 "help"
- `nbp_alc_help_alias`: `help` 指令的别名,默认为 "帮助", "命令帮助"
- `nbp_alc_help_all_alias`: `help` 指令显示隐藏指令时的别名,默认为 "所有帮助", "所有命令帮助"
- `nbp_alc_switch_enable`: `switch` 插件的 `enable` 指令的指令名,默认为 "enable"
- `nbp_alc_switch_enable_alias`: `switch` 插件的 `enable` 指令的别名,默认为 "启用", "启用指令"
- `nbp_alc_switch_disable`: `switch` 插件的 `disable` 指令的指令名,默认为 "disable"
- `nbp_alc_switch_disable_alias`: `switch` 插件的 `disable` 指令的别名,默认为 "disable", "禁用", "禁用指令"
- `nbp_alc_with_text`: `with` 插件的指令名,默认为 "with"
- `nbp_alc_with_alias`: `with` 插件的别名,默认为 "局部前缀"
## 内置匹配拓展
目前插件提供了 5 个内置的 `Extension`,它们在 `nonebot_plugin_alconna.builtins.extensions` 下:
### ReplyRecordExtension
`ReplyRecordExtension` 可将消息事件中的回复暂存在 extension 中,使得解析用的消息不带回复信息,同时可以在后续的处理中获取回复信息:
```python
from nonebot_plugin_alconna import MsgId, on_alconna
from nonebot_plugin_alconna.builtins.extensions import ReplyRecordExtension
matcher = on_alconna("...", extensions=[ReplyRecordExtension()])
@matcher.handle()
async def handle(msg_id: MsgId, ext: ReplyRecordExtension):
if reply := ext.get_reply(msg_id):
...
else:
...
```
### ReplyMergeExtension
`ReplyMergeExtension` 可将消息事件中的回复指向的原消息合并到当前消息中作为一部分参数:
```python
from nonebot_plugin_alconna import Match, on_alconna
from nonebot_plugin_alconna.builtins.extensions.reply import ReplyMergeExtension
matcher = on_alconna("...", extensions=[ReplyMergeExtension()])
@matcher.handle()
async def handle(content: Match[str]):
...
```
其构造时可传入两个参数:
- `add_left`: 否在当前消息的左侧合并回复消息,默认为 False
- `sep`: 合并时的分隔符,默认为空格
### DiscordSlashExtension
`DiscordSlashExtension` 可自动将 Alconna 对象翻译成 Discord 的 slash 指令并注册,且将收到的指令交互事件转为指令供命令解析:
```python
from nonebot_plugin_alconna import Match, on_alconna
from nonebot_plugin_alconna.builtins.extensions.discord import DiscordSlashExtension
alc = Alconna(
["/"],
"permission",
Subcommand("add", Args["plugin", str]["priority?", int]),
Option("remove", Args["plugin", str]["time?", int]),
meta=CommandMeta(description="权限管理"),
)
matcher = on_alconna(alc, extensions=[DiscordSlashExtension()])
@matcher.assign("add")
async def add(plugin: Match[str], priority: Match[int], ext: DiscordSlashExtension):
await ext.send_followup_msg(f"added {plugin.result} with {priority.result if priority.available else 0}")
@matcher.assign("remove")
async def remove(plugin: Match[str], time: Match[int]):
await matcher.finish(f"removed {plugin.result} with {time.result if time.available else -1}")
```
### MarkdownOutputExtension
`MarkdownOutputExtension` 可将 Alconna 的自动输出转换为 Markdown 格式
其构造时可传入两个参数:
- `escape_dot`: 是否转义句中的点号(用来避免被识别为 url
- `text_to_image` 将文本转换为图片的函数,可不传入。一般用来设置渲染 markdown 为图片的函数
### TelegramSlashExtension
`TelegramSlashExtension` 可将 Alconna 的命令注册在 Telegram 上以获得提示,类似于 `DiscordSlashExtension`。
```python
from nonebot_plugin_alconna import on_alconna
from nonebot.adapters.telegram.model import BotCommandScopeChat
from nonebot_plugin_alconna.builtins.extensions.telegram import TelegramSlashExtension
TelegramSlashExtension.set_scope(BotCommandScopeChat())
matcher = on_alconna("...", extensions=[TelegramSlashExtension()])
```
## 内置自定义消息段
目前插件提供了 3 个内置的 `Segment`,它们在 `nonebot_plugin_alconna.builtins.segments` 下:
- `Markdown`: 可以传入 **markdown模板** 的元素
- `MarketFace`: 特指 QQ 的商城表情
- `MusicShare`: 特指 QQ 的音乐分享卡片

View File

@ -0,0 +1,664 @@
---
sidebar_position: 2
description: Alconna 基本介绍
---
# Alconna 本体
[`Alconna`](https://github.com/ArcletProject/Alconna) 隶属于 `ArcletProject`,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
我们先通过一个例子来讲解 **Alconna** 的核心 —— `Args`, `Subcommand`, `Option`
```python
from arclet.alconna import Alconna, Args, Subcommand, Option
alc = Alconna(
"pip",
Subcommand(
"install",
Args["package", str],
Option("-r|--requirement", Args["file", str]),
Option("-i|--index-url", Args["url", str]),
)
)
res = alc.parse("pip install nonebot2 -i URL")
print(res)
# matched=True, header_match=(origin='pip' result='pip' matched=True groups={}), subcommands={'install': (value=Ellipsis args={'package': 'nonebot2'} options={'index-url': (value=None args={'url': 'URL'})} subcommands={})}, other_args={'package': 'nonebot2', 'url': 'URL'}
print(res.all_matched_args)
# {'package': 'nonebot2', 'url': 'URL'}
```
这段代码通过`Alconna`创捷了一个接受主命令名为`pip`, 子命令为`install`且子命令接受一个 **Args** 参数`package`和二个 **Option** 参数`-r``-i`的命令参数解析器, 通过`parse`方法返回解析结果 **Arparma** 的实例。
## 命令头
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 !help 中的 ! 与 help。
命令构造时, `Alconna([prefix], command)``Alconna(command, [prefix])` 是等价的。
| 前缀 | 命令名 | 匹配内容 | 说明 |
| :--------------------------: | :--------: | :---------------------------------------------------------: | :--------------: |
| 不传入 | "foo" | `"foo"` | 无前缀的纯文字头 |
| 不传入 | 123 | `123` | 无前缀的元素头 |
| 不传入 | "re:\d{2}" | `"32"` | 无前缀的正则头 |
| 不传入 | int | `123``"456"` | 无前缀的类型头 |
| [int, bool] | 不传入 | `True``123` | 无名的元素类头 |
| ["foo", "bar"] | 不传入 | `"foo"``"bar"` | 无名的纯文字头 |
| ["foo", "bar"] | "baz" | `"foobaz"``"barbaz"` | 纯文字头 |
| [int, bool] | "foo" | `[123, "foo"]``[False, "foo"]` | 类型头 |
| [123, 4567] | "foo" | `[123, "foo"]``[4567, "foo"]` | 元素头 |
| [nepattern.NUMBER] | "bar" | `[123, "bar"]``[123.456, "bar"]` | 表达式头 |
| [123, "foo"] | "bar" | `[123, "bar"]``"foobar"``["foo", "bar"]` | 混合头 |
| [(int, "foo"), (456, "bar")] | "baz" | `[123, "foobaz"]``[456, "foobaz"]``[456, "barbaz"]` | 对头 |
对于无前缀的类型头,此时会将传入的值尝试转为 BasePattern例如 `int` 会转为 `nepattern.INTEGER`。如此该命令头会匹配对应的类型, 例如 `int` 会匹配 `123``"456"`,但不会匹配 `"foo"`。解析后Alconna 会将命令头匹配到的值转为对应的类型,例如 `int` 会将 `"123"` 转为 `123`
:::tip
**正则内容只在命令名上生效,前缀中的正则会被转义**
:::
除了通过传入 `re:xxx` 来使用正则表达式外Alconna 还提供了一种更加简洁的方式来使用正则表达式,称为 Bracket Header
```python
alc = Alconna(".rd{roll:int}")
assert alc.parse(".rd123").header["roll"] == 123
```
Bracket Header 类似 python 里的 f-string 写法,通过 `"{}"` 声明匹配类型
`"{}"` 中的内容为 "name:type or pat"
- `"{}"`, `"{:}"``"(.+)"`, 占位符
- `"{foo}"``"(?P&lt;foo&gt;.+)"`
- `"{:\d+}"``"(\d+)"`
- `"{foo:int}"``"(?P&lt;foo&gt;\d+)"`,其中 `"int"` 部分若能转为 `BasePattern` 则读取里面的表达式
## 参数声明(Args)
`Args` 是用于声明命令参数的组件, 可以通过以下几种方式构造 **Args**
- `Args[key, var, default][key1, var1, default1][...]`
- `Args[(key, var, default)]`
- `Args.key[var, default]`
其中key **一定**是字符串,而 var 一般为参数的类型default 为具体的值或者 **arclet.alconna.args.Field**
其与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
### key
`key` 的作用是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
其有三种为 Args 注解的标识符: `?``/``!`, 标识符与 key 之间建议以 `;` 分隔:
- `!` 标识符表示该处传入的参数应**不是**规定的类型,或**不在**指定的值中。
- `?` 标识符表示该参数为**可选**参数,会在无参数匹配时跳过。
- `/` 标识符表示该参数的类型注解需要隐藏。
另外,对于参数的注释也可以标记在 `key` 中,其与 key 或者标识符 以 `#` 分割:
`foo#这是注释;?``foo?#这是注释`
:::tip
`Args` 中的 `key` 在实际命令中并不需要传入keyword 参数除外):
```python
from arclet.alconna import Alconna, Args
alc = Alconna("test", Args["foo", str])
alc.parse("test --foo abc") # 错误
alc.parse("test abc") # 正确
```
若需要 `test --foo abc`,你应该使用 `Option`
```python
from arclet.alconna import Alconna, Args, Option
alc = Alconna("test", Option("--foo", Args["foo", str]))
```
:::
### var
var 负责命令参数的**类型检查**与**类型转化**
`Args``var`表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例:
```python
from arclet.alconna import Args
from nepattern import BasePattern
# 表示 foo 参数需要匹配一个 @number 样式的字符串
args = Args["foo", BasePattern("@\d+")]
```
`pip` 示例中可以传入 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`
`nepattern.global_patterns`默认支持的类型有:
- `str`: 匹配任意字符串
- `int`: 匹配整数
- `float`: 匹配浮点数
- `bool`: 匹配 `True``False` 以及他们小写形式
- `hex`: 匹配 `0x` 开头的十六进制字符串
- `url`: 匹配网址
- `email`: 匹配 `xxxx@xxx` 的字符串
- `ipv4`: 匹配 `xxx.xxx.xxx.xxx` 的字符串
- `list`: 匹配类似 `["foo","bar","baz"]` 的字符串
- `dict`: 匹配类似 `{"foo":"bar","baz":"qux"}` 的字符串
- `datetime`: 传入一个 `datetime` 支持的格式字符串,或时间戳
- `Any`: 匹配任意类型
- `AnyString`: 匹配任意类型,转为 `str`
- `Number`: 匹配 `int``float`,转为 `int`
同时可以使用 typing 中的类型:
- `Literal[X]`: 匹配其中的任意一个值
- `Union[X, Y]`: 匹配其中的任意一个类型
- `Optional[xxx]`: 会自动将默认值设为 `None`,并在解析失败时使用默认值
- `List[X]`: 匹配一个列表,其中的元素为 `X` 类型
- `Dict[X, Y]`: 匹配一个字典,其中的 key 为 `X` 类型value 为 `Y` 类型
- ...
:::tip
几类特殊的传入标记:
- `"foo"`: 匹配字符串 "foo" (若没有某个 `BasePattern` 与之关联)
- `RawStr("foo")`: 匹配字符串 "foo" (即使有 `BasePattern` 与之关联也不会被替换)
- `"foo|bar|baz"`: 匹配 "foo" 或 "bar" 或 "baz"
- `[foo, bar, Baz, ...]`: 匹配其中的任意一个值或类型
- `Callable[[X], Y]`: 匹配一个参数为 `X` 类型的值,并返回通过该函数调用得到的 `Y` 类型的值
- `"re:xxx"`: 匹配一个正则表达式 `xxx`,会返回 Match[0]
- `"rep:xxx"`: 匹配一个正则表达式 `xxx`,会返回 `re.Match` 对象
- `{foo: bar, baz: qux}`: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)
- ...
**特别的**,你可以不传入 `var`,此时会使用 `key` 作为 `var`, 匹配 `key` 字符串。
:::
#### MultiVar 与 KeyWordVar
`MultiVar` 是一个特殊的标注,用于告知解析器该参数可以接受多个值,类似于函数中的 `*args`,其构造方法形如 `MultiVar(str)`
同样的还有 `KeyWordVar`,类似于函数中的 `*, name: type`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
:::tip
`MultiVar``KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,类似于函数中的 `**kwargs`,其构造方法形如 `MultiVar(KeyWordVar(str))`
`MultiVar``KeyWordVar` 也可以传入 `default` 参数,用于指定默认值
`MultiVar` 不能在 `KeyWordVar` 之后传入
:::
#### AllParam
`AllParam` 是一个特殊的标注,用于告知解析器该参数接收命令中在此位置之后的所有参数并**结束解析**,可以认为是**泛匹配参数**。
`AllParam` 可直接使用 (`Args["xxx", AllParam]`), 也可以传入指定的接收类型 (`Args["xxx", AllParam(str)]`)。
:::tip
`nonebot_plugin_alconna` 下,`AllParam` 的返回值为 [`UniMessage`](./uniseg/message.mdx)
:::
### default
`default` 传入的是该参数的默认值或者 `Field`,以携带对于该参数的更多信息。
默认情况下 (即不声明) `default` 的值为特殊值 `Empty`。这也意味着你可以将默认值设置为 `None` 表示默认值为空值。
`Field` 构造需要的参数说明如下:
- default: 参数单元的默认值
- alias: 参数单元默认值的别名
- completion: 参数单元的补全说明生成函数
- unmatch_tips: 参数单元的错误提示生成函数,其接收一个表示匹配失败的元素的参数
- missing_tips: 参数单元的缺失提示生成函数
## 选项与子命令(Option & Subcommand)
`Option``Subcommand` 可以传入一组 `alias`,如 `Option("--foo|-F|--FOO|-f")``Subcommand("foo", alias=["F"])`
传入别名后,选项与子命令会选择其中长度最长的作为其名称。若传入为 "--foo|-f",则命令名称为 "--foo"
:::tip 特别提醒!!!
Option 的名字或别名**没有要求**必须在前面写上 `-`
Option 与 Subcommand 的唯一区别在于 Subcommand 可以传入自己的 **Option****Subcommand**
:::
他们拥有如下共同参数:
- `help_text`: 传入该组件的帮助信息
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
对于命令 `test foo bar baz qux <a:int>` 来讲,因为`foo bar baz` 仅需要判断是否相等, 所以可以这么编写:
```python
Alconna("test", Option("qux", Args.a[int], requires=["foo", "bar", "baz"]))
```
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
特别的,使用 `OptionResult``SubcomanndResult` 可以设置包括参数字典在内的默认值:
```python
from arclet.alconna import Option, OptionResult
opt1 = Option("--foo", default=False)
opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
```
### Action
`Option` 可以特别设置传入一类 `Action`,作为解析操作
`Action` 分为三类:
- `store`: 无 Args 时, 仅存储一个值, 默认为 Ellipsis 有 Args 时, 后续的解析结果会覆盖之前的值
- `append`: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis 有 Args 时, 每个解析结果会追加到列表中, 当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
- `count`: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同, 当存在默认值并且不为数字时, 会自动将默认值变成 1 以保证计数器的正确性。
`Alconna` 提供了预制的几类 `Action`
- `store`(默认)`store_value``store_true``store_false`
- `append``append_value`
- `count`
## 解析结果
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果
`Arparma` 有如下属性:
- 调试类
- matched: 是否匹配成功
- error_data: 解析失败时剩余的数据
- error_info: 解析失败时的异常内容
- origin: 原始命令,可以类型标注
- 分析类
- header_match: 命令头部的解析结果,包括原始头部、解析后头部、解析结果与可能的正则匹配组
- main_args: 命令的主参数的解析结果
- options: 命令所有选项的解析结果
- subcommands: 命令所有子命令的解析结果
- other_args: 除主参数外的其他解析结果
- all_matched_args: 所有 Args 的解析结果
### 路径查询
`Arparma` 同时提供了便捷的查询方法 `query[type]()`,会根据传入的 `path` 查找参数并返回
`path` 支持如下:
- `main_args`, `options`, ...: 返回对应的属性
- `args`: 返回 all_matched_args
- `args.<key>`: 返回 all_matched_args 中 `key` 键对应的值
- `main_args.<key>`: 返回主命令的解析参数字典中 `key` 键对应的值
- `<node>`: 返回选项/子命令 `node` 的解析结果 (OptionResult | SubcommandResult)
- `<node>.value`: 返回选项/子命令 `node` 的解析值
- `<node>.args`: 返回选项/子命令 `node` 的解析参数字典
- `<node>.<key>`, `<node>.args.<key>`: 返回选项/子命令 `node` 的参数字典中 `key` 键对应的值
以及:
- `options.<opt>`: 返回选项 `opt` 的解析结果 (OptionResult)
- `options.<opt>.value`: 返回选项 `opt` 的解析值
- `options.<opt>.args`: 返回选项 `opt` 的解析参数字典
- `options.<opt>.<key>`, `options.<node>.args.<key>`: 返回选项 `opt` 的参数字典中 `key` 键对应的值
- `subcommands.<subcmd>`: 返回子命令 `subcmd` 的解析结果 (SubcommandResult)
- `subcommands.<subcmd>.value`: 返回子命令 `subcmd` 的解析值
- `subcommands.<subcmd>.args`: 返回子命令 `subcmd` 的解析参数字典
- `subcommands.<subcmd>.<key>`, `subcommands.<node>.args.<key>`: 返回子命令 `subcmd` 的参数字典中 `key` 键对应的值
## 元数据(CommandMeta)
`Alconna` 的元数据相当于其配置,拥有以下条目:
- `description`: 命令的描述
- `usage`: 命令的用法
- `example`: 命令的使用样例
- `author`: 命令的作者
- `fuzzy_match`: 命令是否开启模糊匹配
- `fuzzy_threshold`: 模糊匹配阈值
- `raise_exception`: 命令是否抛出异常
- `hide`: 命令是否对 manager 隐藏
- `hide_shortcut`: 命令的快捷指令是否在 help 信息中隐藏
- `keep_crlf`: 命令解析时是否保留换行字符
- `compact`: 命令是否允许第一个参数紧随头部
- `strict`: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数
- `context_style`: 命令上下文插值的风格None 为关闭bracket 为 `{...}`parentheses 为 `$(...)`
- `extra`: 命令的自定义额外信息
元数据一定使用 `meta=...` 形式传入:
```python
from arclet.alconna import Alconna, CommandMeta
alc = Alconna(..., meta=CommandMeta("foo", example="bar"))
```
## 命名空间配置
命名空间配置 (以下简称命名空间) 相当于 `Alconna` 的默认配置,其优先度低于 `CommandMeta`
`Alconna` 默认使用 "Alconna" 命名空间。
命名空间有以下几个属性:
- name: 命名空间名称
- prefixes: 默认前缀配置
- separators: 默认分隔符配置
- formatter_type: 默认格式化器类型
- fuzzy_match: 默认是否开启模糊匹配
- raise_exception: 默认是否抛出异常
- builtin_option_name: 默认的内置选项名称(--help, --shortcut, --comp)
- disable_builtin_options: 默认禁用的内置选项(--help, --shortcut, --comp)
- enable_message_cache: 默认是否启用消息缓存
- compact: 默认是否开启紧凑模式
- strict: 命令是否严格匹配
- context_style: 命令上下文插值的风格
- ...
### 新建命名空间并替换
```python
from arclet.alconna import Alconna, namespace, Namespace, Subcommand, Args, config
ns = Namespace("foo", prefixes=["/"]) # 创建 "foo"命名空间配置, 它要求创建的Alconna的主命令前缀必须是/
alc = Alconna("pip", Subcommand("install", Args["package", str]), namespace=ns) # 在创建Alconna时候传入命名空间以替换默认命名空间
# 可以通过with方式创建命名空间
with namespace("bar") as np1:
np1.prefixes = ["!"] # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
np1.formatter_type = ShellTextFormatter # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
np1.builtin_option_name["help"] = {"帮助", "-h"} # 设置此命名空间下的命令的帮助选项名称
# 你还可以使用config来管理所有命名空间并切换至任意命名空间
config.namespaces["foo"] = ns # 将命名空间挂载到 config 上
alc = Alconna("pip", Subcommand("install", Args["package", str]), namespace=config.namespaces["foo"]) # 也是同样可以切换到"foo"命名空间
```
### 修改默认的命名空间
```python
from arclet.alconna import config, namespace, Namespace
config.default_namespace.prefixes = [...] # 直接修改默认配置
np = Namespace("xxx", prefixes=[...])
config.default_namespace = np # 更换默认的命名空间
with namespace(config.default_namespace.name) as np:
np.prefixes = [...]
```
## 快捷指令
快捷命令可以做到标识一段命令, 并且传递参数给原命令
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除)
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置:
```python
class ShortcutArgs(TypedDict):
"""快捷指令参数"""
command: NotRequired[str]
"""快捷指令的命令"""
args: NotRequired[list[Any]]
"""快捷指令的附带参数"""
fuzzy: NotRequired[bool]
"""是否允许命令后随参数"""
prefix: NotRequired[bool]
"""是否调用时保留指令前缀"""
wrapper: NotRequired[ShortcutRegWrapper]
"""快捷指令的正则匹配结果的额外处理函数"""
humanized: NotRequired[str]
"""快捷指令的人类可读描述"""
```
### args的使用
```python
from arclet.alconna import Alconna, Args
alc = Alconna("setu", Args["count", int])
alc.shortcut("涩图(\d+)张", {"args": ["{0}"]})
# 'Alconna::setu 的快捷指令: "涩图(\\d+)张" 添加成功'
alc.parse("涩图3张").query("count")
# 3
```
### command的使用
```python
from arclet.alconna import Alconna, Args
alc = Alconna("eval", Args["content", str])
alc.shortcut("echo", {"command": "eval print(\\'{*}\\')"})
# 'Alconna::eval 的快捷指令: "echo" 添加成功'
alc.shortcut("echo", delete=True) # 删除快捷指令
# 'Alconna::eval 的快捷指令: "echo" 删除成功'
@alc.bind() # 绑定一个命令执行器, 若匹配成功则会传入参数, 自动执行命令执行器
def cb(content: str):
eval(content, {}, {})
alc.parse('eval print(\\"hello world\\")')
# hello world
alc.parse("echo hello world!")
# hello world!
```
`fuzzy` 为 False 时,第一个例子中传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
快捷指令允许三类特殊的 placeholder
- `{%X}`: 如 `setu {%0}`,表示此处填入快捷指令后随的第 X 个参数。
例如,若快捷指令为 `涩图`, 配置为 `{"command": "setu {%0}"}`, 则指令 `涩图 1` 相当于 `setu 1`
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
- `{X}`: 表示此处填入可能的正则匹配的组:
-`command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
-`command` 中存储匹配组 `(?P<xxx>...)`, 则 `{X}` 表示 **名字** 为 X 的匹配结果
除此之外, 通过 **Alconna** 内置选项 `--shortcut` 可以动态操作快捷指令
例如:
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
- `cmd --shortcut delete key` 来删除一个快捷指令
```python
from arclet.alconna import Alconna, Args
alc = Alconna("eval", Args["content", str])
alc.shortcut("echo", {"command": "eval print(\\'{*}\\')"})
alc.parse("eval --shortcut list")
# 'echo'
```
## 紧凑命令
`Alconna`, `Option``Subcommand` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
```python
from arclet.alconna import Alconna, Option, CommandMeta, Args
alc = Alconna("test", Args["foo", int], Option("BAR", Args["baz", str], compact=True), meta=CommandMeta(compact=True))
assert alc.parse("test123 BARabc").matched
```
这使得我们可以实现如下命令:
```python
from arclet.alconna import Alconna, Option, Args, append
alc = Alconna("gcc", Option("--flag|-F", Args["content", str], action=append, compact=True))
print(alc.parse("gcc -Fabc -Fdef -Fxyz").query[list]("flag.content"))
# ['abc', 'def', 'xyz']
```
`Option``action``count` 时,其自动支持 `compact` 特性:
```python
from arclet.alconna import Alconna, Option, count
alc = Alconna("pp", Option("--verbose|-v", action=count, default=0))
print(alc.parse("pp -vvv").query[int]("verbose.value"))
# 3
```
## 模糊匹配
模糊匹配会应用在任意需要进行名称判断的地方,如 **命令名称****选项名称** 和 **参数名称** (如指定需要传入参数名称)。
```python
from arclet.alconna import Alconna, CommandMeta
alc = Alconna("test_fuzzy", meta=CommandMeta(fuzzy_match=True))
alc.parse("test_fuzy")
# test_fuzy is not matched. Do you mean "test_fuzzy"?
```
## 半自动补全
半自动补全为用户提供了推荐后续输入的功能
补全默认通过 `--comp``-cp``?` 触发:(命名空间配置可修改名称)
```python
from arclet.alconna import Alconna, Args, Option
alc = Alconna("test", Args["abc", int]) + Option("foo") + Option("bar")
alc.parse("test --comp")
'''
output
以下是建议的输入:
* <abc: int>
* --help
* -h
* -sct
* --shortcut
* foo
* bar
'''
```
## Duplication
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse****Namespace**
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub** 和 **SubcommandStub** 三个部分
以pip为例其对应的 Duplication 应如下构造:
```python
from arclet.alconna import Alconna, Args, Option, OptionResult, Duplication, SubcommandStub, Subcommand, count
class MyDup(Duplication):
verbose: OptionResult
install: SubcommandStub
alc = Alconna(
"pip",
Subcommand(
"install",
Args["package", str],
Option("-r|--requirement", Args["file", str]),
Option("-i|--index-url", Args["url", str]),
),
Option("-v|--version"),
Option("-v|--verbose", action=count),
)
res = alc.parse("pip -v install ...") # 不使用duplication获得的提示较少
print(res.query("install"))
# (value=Ellipsis args={'package': '...'} options={} subcommands={})
result = alc.parse("pip -v install ...", duplication=MyDup)
print(result.install)
# SubcommandStub(_origin=Subcommand('install', args=Args('package': str)), _value=Ellipsis, available=True, args=ArgsStub(_origin=Args('package': str), _value={'package': '...'}, available=True), dest='install', options=[OptionStub(_origin=Option('requirement', args=Args('file': str)), _value=None, available=False, args=ArgsStub(_origin=Args('file': str), _value={}, available=False), dest='requirement', aliases=['r', 'requirement'], name='requirement'), OptionStub(_origin=Option('index-url', args=Args('url': str)), _value=None, available=False, args=ArgsStub(_origin=Args('url': str), _value={}, available=False), dest='index-url', aliases=['index-url', 'i'], name='index-url')], subcommands=[], name='install')
```
**Duplication** 也可以如 **Namespace** 一样直接标明参数名称和类型:
```python
from typing import Optional
from arclet.alconna import Duplication
class MyDup(Duplication):
package: str
file: Optional[str] = None
url: Optional[str] = None
```
## 上下文插值
`context_style` 条目被设置后,传入的命令中符合上下文插值的字段会被自动替换成当前上下文中的信息。
上下文可以在 `parse` 中传入:
```python
from arclet.alconna import Alconna, Args, CommandMeta
alc = Alconna("test", Args["foo", int], meta=CommandMeta(context_style="parentheses"))
alc.parse("test $(bar)", {"bar": 123})
# {"foo": 123}
```
context_style 的值分两种:
- `"bracket"`: 插值格式为 `{...}`,例如 `{foo}`
- `"parentheses"`: 插值格式为 `$(...)`,例如 `$(bar)`

View File

@ -0,0 +1,105 @@
---
sidebar_position: 4
description: 配置项
---
# 配置项
## alconna_auto_send_output
- **类型**: `bool | None`
- **默认值**: `None`
是否全局启用输出信息自动发送,不启用则会在触发特殊内置选项后仍然将解析结果传递至响应器。
## alconna_use_command_start
- **类型**: `bool`
- **默认值**: `False`
是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀
## alconna_global_completion
- **类型**: [`CompConfig | None`](./matcher.mdx#补全会话)
- **默认值**: `None`
全局的补全会话配置 (不代表全局启用补全会话)。
## alconna_use_origin
- **类型**: `bool`
- **默认值**: `False`
是否全局使用原始消息 (即未经过 to_me 等处理的),该选项会影响到 Alconna 的匹配行为。
## alconna_use_command_sep
- **类型**: `bool`
- **默认值**: `False`
是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符。
## alconna_global_extensions
- **类型**: `list[str]`
- **默认值**: `[]`
全局加载的扩展,其读取路径以 . 分隔,如 `foo.bar.baz:DemoExtension`
对于内置扩展,路径为 `nonebot_plugin_alconna.builtins.extensions` 下的模块名,如 `ReplyMergeExtension`,可以使用 `@` 来缩写路径,
`@reply:ReplyMergeExtension`
## alconna_context_style
- **类型**: `Optional[Literal["bracket", "parentheses"]]`
- **默认值**: `None`
全局命令上下文插值的风格None 为关闭bracket 为 `{...}`parentheses 为 `$(...)`
## alconna_enable_saa_patch
- **类型**: `bool`
- **默认值**: `False`
是否启用 SAA 补丁。
## alconna_apply_filehost
- **类型**: `bool`
- **默认值**: `False`
是否启用文件托管。
## alconna_apply_fetch_targets
- **类型**: `bool`
- **默认值**: `False`
是否启动时拉取一次[发送对象](./uniseg/utils.mdx#发送对象)列表。
## alconna_builtin_plugins
- **类型**: `set[str]`
- **默认值**: `set()`
需要加载的内置插件列表。
## alconna_conflict_resolver
- **类型**: `Literal["raise", "default", "ignore", "replace"]`
- **默认值**: `"default"`
命令冲突解决策略,决定当不同插件之间或者同一插件之间存在两个以上相同的命令时的处理方式:
- `default`: 默认处理方式,保留两个命令
- `raise`: 抛出异常
- `ignore`: 忽略较新的命令
- `replace`: 替换较旧的命令
## alconna_response_self
- **类型**: `bool`
- **默认值**: `False`
是否让响应器处理由 bot 自身发送的消息。

View File

@ -0,0 +1,678 @@
---
sidebar_position: 3
description: 响应规则的使用
---
import Messenger from "@site/src/components/Messenger";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
# `on_alconna` 响应器
`nonebot_plugin_alconna` 插件本体的大部分功能都围绕着 `on_alconna` 响应器展开。
该响应器类似于 `on_command`,基于 `Alconna` 解析器来解析命令。
以下是一个简单的 `on_alconna` 响应器的例子:
```python
from nonebot_plugin_alconna import At, Image, Match, on_alconna
from arclet.alconna import Args, Option, Alconna, MultiVar, Subcommand
alc = Alconna(
"role-group",
Subcommand(
"add|添加",
Args["name", str],
Option("member", Args["target", MultiVar(At)]),
dest="add",
compact=True,
),
Option("list"),
Option("icon", Args["icon", Image])
)
rg = on_alconna(alc, use_command_start=True, aliases={"角色组"})
@rg.assign("list")
async def list_role_group():
img: bytes = await gen_role_group_list_image()
await rg.finish(Image(raw=img))
@rg.assign("add")
async def _(name: str, target: Match[tuple[At, ...]]):
group = await create_role_group(name)
if target.available:
ats: tuple[At, ...] = target.result
group.extend(member.target for member in ats)
await rg.finish("添加成功")
```
<Messenger
msgs={[
{ position: "right", msg: "/role-group list" },
{
position: "left",
msg: "[图片]",
},
{ position: "right", msg: "/角色组 添加foo @bar @baz" },
{ position: "left", msg: "添加成功" },
]}
/>
## 声明
`on_alconna` 的参数如下:
```python
def on_alconna(
command: Alconna | str,
rule: Rule | T_RuleChecker | None = None,
skip_for_unmatch: bool = True,
auto_send_output: bool | None = None,
aliases: set[str] | tuple[str, ...] | None = None,
comp_config: CompConfig | None = None,
extensions: list[type[Extension] | Extension] | None = None,
exclude_ext: list[type[Extension] | str] | None = None,
use_origin: bool | None = None,
use_cmd_start: bool | None = None,
use_cmd_sep: bool | None = None,
response_self: bool | None = None,
**kwargs: Any,
) -> type[AlconnaMatcher]:
...
```
- `command`: Alconna 命令或字符串,字符串将通过 `AlconnaFormat` 转换为 Alconna 命令
- `rule`: 事件响应规则, 详见 [响应器规则](../../advanced/matcher.md#事件响应规则)
- `skip_for_unmatch`: 是否在命令不匹配时跳过该响应, 默认为 `True`
- `auto_send_output`: 是否自动发送输出信息并跳过该响应。
- `True`:自动发送输出信息并跳过该响应
- `False`:不自动发送输出信息,而是传递进行处理
- `None`:跟随全局配置项 `alconna_auto_send_output`,默认值为 `True`
- `aliases`: 命令别名, 作用类似于 `on_command` 中的 aliases
- `comp_config`: 补全会话配置, 不传入则不启用补全会话
- `extensions`: 需要加载的匹配扩展, 可以是扩展类或扩展实例
- `exclude_ext`: 需要排除的匹配扩展, 可以是扩展类或扩展的id
- `use_origin`: 是否使用未经 to_me 等处理过的消息。`None` 时跟随全局配置项 `alconna_use_origin`,默认值为 `False`
- `use_cmd_start`: 是否使用 COMMAND_START 作为命令前缀。`None` 时跟随全局配置项 `alconna_use_command_start`,默认值为 `False`
- `use_cmd_sep`: 是否使用 COMMAND_SEP 作为命令分隔符。`None` 时跟随全局配置项 `alconna_use_command_sep`,默认值为 `False`
- `response_self`: 是否响应自身消息。`None` 时跟随全局配置项 `alconna_response_self`,默认值为 `False`
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher` ,其拓展了如下方法:
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理
- `.dispatch`: 同样的分派处理,但是是类似 `CommandGroup` 一样返回新的 `AlconnaMatcher`
- `.got_path(path, prompt, middleware)`: 在 `got` 方法的基础上,会以 path 对应的参数为准,读取传入 message 的最后一个消息段并验证转换
- `.got`, `send`, `reject`, ... : 拓展了 prompt 类型,即支持使用 `UniMessage` 作为 prompt
- ...
除了标准的创建方式,本插件也提供了 `funcommand` 和 `Command` 两种快捷方式来创建 `AlconnaMatcher` 详见 [快捷方式](./shortcut.md)。
## 依赖注入
`AlconnaMatcher` 的特性之一是拓展了依赖注入的功能。
### 注入模型
插件提供了几种用来处理解析结果的模型:
- `CommandResult`: 用于快捷访问命令解析结果
- `result (Arparma)`: 解析结果
- `source (Alconna)`: 源命令
- `matched (bool)`: 是否匹配
- `context (dict)`: 命令的上下文
- `output (str | None)`: 命令的输出
- `Match`: 匹配项,表示参数是否存在于 `Arparma.all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
- `Match` 只能查找到 `Arparma.all_matched_args` 中的参数。对于特定选项/子命令的参数,需要使用 `Query` 来查询
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
- `Query` 除了查询参数,也可以查询某个选项/子命令是否存在
### 编写
```python
async def handle(
result: CommandResult,
arp: Arparma,
dup: Duplication,
source: Alconna,
ext: Extension,
exts: SelectedExtensions,
abc: str,
foo: Match[str],
bar: Query[int] = Query("ttt.bar", 0)
):
...
```
`AlconnaMatcher` 的依赖注入拓展支持以下情况:
- `xxx: CommandResult`
- `xxx: Arparma`:命令的[解析结果](./command.md#解析结果)
- `xxx: Duplication`:命令的解析结果的 [`Duplication`](./command.md#Duplication)
- `xxx: Alconna`:命令的源命令
- `<key>: Match[<type>]`:上述的匹配项,使用 `key` 作为查询路径
- `xxx: Query[<type>] = Query(<path>, default)`:上述的查询项,必需声明默认值以设置查询路径 `path`
- 当用来查询选项/子命令是否存在时,可不写 `Query[<type>]`
- `xxx: Extension`:当前 `AlconnaMatcher` 使用的指定类型的匹配扩展
- `xxx: SelectedExtensions`:当前 `AlconnaMatcher` 使用的所有可用的匹配扩展
- `<key>: <type>`: 其他情况
- 当 `key` 的名称是 "ctx" 或 "context" 并且类型为 `dict` 时,会注入命令的上下文
- 当 `key` 存在于命令的上下文中时,会注入对应的值
- 当 `key` 存在于 `Arparma` 的 `all_matched_args` 中时,会注入对应的值, 类似于 `Match` 的用法,但当该值不存在时将跳过响应器。
- 当 `key` 属于 `got_path` 的参数时,会注入对应的值
- 当 `key` 被某个 `Extension.before_catch` 确认为需要注入的参数时,会调用 `Extension.catch` 来注入对应的值
:::note
如果你更喜欢 Depends 式的依赖注入,`nonebot_plugin_alconna` 同时提供了一系列的依赖注入函数,他们包括:
- `AlconnaResult`: `CommandResult` 类型的依赖注入函数
- `AlconnaMatches`: `Arparma` 类型的依赖注入函数
- `AlconnaDuplication`: `Duplication` 类型的依赖注入函数
- `AlconnaMatch`: `Match` 类型的依赖注入函数,其能够额外传入一个 middleware 函数来处理得到的参数
- `AlconnaQuery`: `Query` 类型的依赖注入函数,其能够额外传入一个 middleware 函数来处理得到的参数
- `AlconnaExecResult`: 提供挂载在命令上的 callback 的返回结果 (`Dict[str, Any]`) 的依赖注入函数
- `AlconnaExtension`: 提供指定类型的 `Extension` 的依赖注入函数
:::
示例:
```python
from nonebot import require
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import AlconnaQuery, AlcResult, Match, Query, on_alconna
from arclet.alconna import Alconna, Args, Option, Arparma
test = on_alconna(
Alconna(
"test",
Option("foo", Args["bar", int]),
Option("baz", Args["qux", bool, False])
)
)
@test.handle()
async def handle_test1(result: AlcResult):
await test.send(f"matched: {result.matched}")
await test.send(f"maybe output: {result.output}")
@test.handle()
async def handle_test2(result: Arparma):
await test.send(f"head result: {result.header_result}")
await test.send(f"args: {result.all_matched_args}")
@test.handle()
async def handle_test3(bar: Match[int]):
if bar.available:
await test.send(f"foo={bar.result}")
@test.handle()
async def handle_test4(qux: Query[bool] = AlconnaQuery("baz.qux", False)):
if qux.available:
await test.send(f"baz.qux={qux.result}")
```
## 条件控制
### `assign` 方法
`AlconnaMatcher` 的 `assign` 方法与 `handle` 类似,但是可以控制响应函数是否在不满足条件时跳过响应。
`assign` 方法的参数如下:
```python
def assign(
cls,
path: str,
value: Any = _seminal,
or_not: bool = False,
additional: CHECK | None = None,
parameterless: Iterable[Any] | None = None,
):
...
```
- `path`: 指定的[查询路径](./command.md#路径查询)
- "$main" 表示没有任何选项/子命令匹配的时候
- "\~XX" 时会把 "\~" 替换为父级路径
- `value`: 可能的指定查询值
- `or_not`: 是否同时处理没有查询成功的情况
- `additional`: 额外的条件检查函数
例如:
```python
# 处理没有任何选项/子命令匹配的情况
@rg.assign("$main")
async def handle_main(): ...
# 处理 list 选项
@rg.assign("list")
async def handle_list(): ...
# 处理 add 选项,且 name 为 admin
@rg.assign("add.name", "admin")
async def handle_add_admin(): ...
```
### `dispatch` 方法
此外,使用 `.dispatch` 还能像 `CommandGroup` 一样为每个分发设置独立的 matcher
```python
rg_list_cmd = rg.dispatch("list")
@rg_list_cmd.handle()
async def handle_list(): ...
```
`dispatch` 的参数与 `assign` 相同。
当使用 `dispatch` 时,父级路径表示为传入 `dispatch` 的 `path`:
```python
rg_add_cmd = rg.dispatch("add")
# 此时 ~name 表示 add.name
@rg_add_cmd.assign("~name", "admin")
async def handle_add_admin(): ...
```
:::tip
在 `dispatch` 下, `Query` 的 `path` 也同样支持 `~` 前缀来表示父级路径
```python
@rg_add_cmd.assign("~name", "admin")
async def handle_add_admin(target: Query[tuple[At, ...]] = Query("~target")):
if target.available:
await rg.send(f"添加成功: {target.result}")
```
:::
### `got_path` 方法
另外,`AlconnaMatcher` 有类似于 [`got`](../../appendices/session-control.mdx#got) 的 `got_path` 与配套的 `get_path_arg`, `set_path_arg`
```python
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
test_cmd = on_alconna(Alconna("test", Args["target?", Union[str, At]]))
@test_cmd.handle()
async def tt_h(target: Match[Union[str, At]]):
if target.available:
test_cmd.set_path_arg("target", target.result)
@test_cmd.got_path("target", prompt="请输入目标")
async def tt(target: Union[str, At]):
await test_cmd.send(UniMessage(["ok\n", target]))
```
`got_path` 与 `assign``Match``Query` 等地方一样,都需要指明 `path` 参数 (即对应 Arg 验证的路径)
`got_path` 会获取消息的最后一个消息段并转为 path 对应的类型,例如示例中 `target` 对应的 Arg 里要求 str 或 At则 got 后用户输入的消息只有为 text 或 at 才能进入处理函数。
`got_path` 中可以使用依赖注入函数 `AlconnaArg`, 类似于 [`Arg`](../../advanced/dependency.mdx#arg).
### `prompt` 方法
基于 [`Waiter`](https://github.com/RF-Tar-Railt/nonebot-plugin-waiter) 插件,`AlconnaMatcher` 提供了 `prompt` 方法来实现更灵活的交互式提示。
```python
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
test_cmd = on_alconna(Alconna("test", Args["target?", Union[str, At]]))
@test_cmd.handle()
async def tt_h(target: Match[Union[str, At]]):
if target.available:
await test_cmd.finish(UniMessage(["ok\n", target]))
resp = await test_cmd.prompt("请输入目标", timeout=30) # 等待 30 秒
if resp is None:
await test_cmd.finish("超时")
await test_cmd.finish(UniMessage(["ok\n", resp[-1]]))
```
## 返回值中间件
在 `AlconnaMatch``AlconnaQuery` 或 `got_path` 中,你可以使用 `middleware` 参数来传入一个对返回值进行处理的函数:
```python
from nonebot_plugin_alconna import image_fetch
mask_cmd = on_alconna(Alconna("search", Args["img?", Image]))
@mask_cmd.handle()
async def mask_h(matcher: AlconnaMatcher, img: Match[bytes] = AlconnaMatch("img", image_fetch)):
result = await search_img(img.result)
await matcher.send(result.content)
```
其中,`image_fetch` 是一个中间件,其接受一个 `Image` 对象,并提取图片的二进制数据返回。
## i18n
本插件基于 `tarina.lang` 模块提供了 i18n 的支持,参见 [Lang 用法](https://github.com/nonebot/plugin-alconna/discussions/50)。
当你编写完语言文件后,你便可以通过 `AlconnaMatcher.i18n` 来快速地将语言文件中的内容转为 UniMessage.
<Tabs groupId="i18n">
<TabItem value="zh" label="中文">
```yaml title="zh-CN.yml"
# 中文语言文件
demo:
command:
role-group:
add: 添加 {name} 成功!
```
<Messenger
msgs={[
{ position: "right", msg: "/角色组 添加 foo" },
{ position: "left", msg: "添加 foo 成功!" },
]}
/>
</TabItem>
<TabItem value="en" label="英文">
```yaml title="en-US.yml"
# 英文语言文件
demo:
command:
role-group:
add: Add {name} successfully!
```
<Messenger
msgs={[
{ position: "right", msg: "/role-group add foo" },
{ position: "left", msg: "Add foo successfully!" },
]}
/>
</TabItem>
</Tabs>
```python title="使用 i18n"
@rg.assign("add")
async def handle_add(name: str):
await rg.i18n("demo", "command.role-group.add", name=name).finish()
```
## 匹配测试
`AlconnaMatcher.test` 方法允许你在 NoneBot 启动时对命令进行测试。
```python
def test(
cls,
message: str | UniMessage,
expected: dict[str, Any] | None = None,
prefix: bool = True
): ...
```
- `message`: 测试的消息
- `expected`: 预期的解析结果,若为 None 则表示只测试是否匹配
- `prefix`: 是否使用命令前缀,默认为 True
## 匹配拓展
本插件提供了一个 `Extension` 类,其用于自定义 AlconnaMatcher 的部分行为
目前 `Extension` 的功能有:
- `validate`: 对于事件的来源适配器或 bot 选择是否接受响应
- `output_converter`: 输出信息的自定义转换方法
- `message_provider`: 从传入事件中自定义提取消息的方法
- `receive_provider`: 对传入的消息 (UniMessage) 的额外处理
- `context_provider`: 对命令上下文的额外处理
- `permission_check`: 命令对消息解析并确认头部匹配(即确认选择响应)时对发送者的权限判断
- `parse_wrapper`: 对命令解析结果的额外处理
- `send_wrapper`: 对发送的消息 (Message 或 UniMessage) 的额外处理
- `before_catch`: 自定义依赖注入的绑定确认函数
- `catch`: 自定义依赖注入处理函数
- `post_init`: 响应器创建后对命令对象的额外处理
:::tip
Extension 可以通过 `add_global_extension` 方法来全局添加。
```python
from nonebot_plugin_alconna import add_global_extension
from nonebot_plugin_alconna.builtins.extensions.telegram import TelegramSlashExtension
add_global_extension(TelegramSlashExtension)
```
全局的 Extension 可延迟加载 (即若有全局拓展加载于部分 AlconnaMatcher 之后,这部分响应器会被追加拓展)
:::
例如一个 `LLMExtension` 可以如下实现 (仅举例)
```python
from nonebot_plugin_alconna import Extension, Alconna, on_alconna, Interface
class LLMExtension(Extension):
@property
def priority(self) -> int:
return 10
@property
def id(self) -> str:
return "LLMExtension"
def __init__(self, llm):
self.llm = llm
def post_init(self, alc: Alconna) -> None:
self.llm.add_context(alc.command, alc.meta.description)
async def receive_wrapper(self, bot, event, receive):
resp = await self.llm.input(str(receive))
return receive.__class__(resp.content)
def before_catch(self, name, annotation, default):
return name == "llm"
def catch(self, interface: Interface):
if interface.name == "llm":
return self.llm
matcher = on_alconna(
Alconna(...),
extensions=[LLMExtension(LLM)]
)
...
```
那么添加了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量。
### validate
```python
def validate(self, bot: Bot, event: Event) -> bool: ...
```
默认情况下,`validate` 方法会筛选 `event.get_type()` 为 `message` 的情况,表示接受消息事件。
### output_converter
```python
async def output_converter(self, output_type: OutputType, content: str) -> UniMessage: ...
```
依据输出信息的类型,将字符串转换为消息对象以便发送。
其中 `OutputType` 为 "help", "shortcut", "completion", "error" 其中之一。
该方法只会调用一次,即对于多个 Extension选择优先级靠前且实现了该方法的 Extension。
### message_provider
```python
async def message_provider(
self, event: Event, state: T_State, bot: Bot, use_origin: bool = False
) -> UniMessage | None:...
```
该方法用于从事件中提取消息,默认情况下会使用 `event.get_message()` 来获取消息。
该方法可能会调用多次,即对于多个 Extension选择优先级靠前且实现了该方法的 Extension若调用的返回值不为 `None` 则作为结果。
:::caution
该方法的默认实现对结果 (UniMessage) 会进行缓存。`Extension` 的实现也应尽量实现缓存机制。
:::
### receive_provider
```python
async def receive_provider(self, bot: Bot, event: Event, command: Alconna, receive: UniMessage) -> UniMessage: ...
```
该方法用于对传入的消息 (UniMessage) 进行额外处理,默认情况下会返回原始消息。
该方法会调用多次,即对于多个 Extension前一个 Extension 的返回值会作为下一个 Extension 的输入。
### context_provider
```python
async def context_provider(self, ctx: dict[str, Any], bot: Bot, event: Event, state: T_State) -> dict[str, Any]:
```
该方法用于提取命令上下文,默认情况下会返回 `ctx` 本身。
该方法会调用多次,即对于多个 Extension前一个 Extension 的返回值会作为下一个 Extension 的输入。
### permission_check
```python
async def permission_check(self, bot: Bot, event: Event, command: Alconna) -> bool: ...
```
该方法用于对发送者的权限进行检查,默认情况下会返回 `True`。
该方法可能会调用多次,即对于多个 Extension若调用的返回值不为 `True` 则结束判断。
### parse_wrapper
```python
async def parse_wrapper(self, bot: Bot, state: T_State, event: Event, res: Arparma) -> None: ...
```
该方法用于对命令解析结果进行额外处理。
该方法会调用多次,即对于多个 Extension会并发地调用该方法。
### send_wrapper
```python
async def send_wrapper(self, bot: Bot, event: Event, send: TMessage) -> TMessage: ...
```
该方法用于对 `AlconnaMatcher.send` 或 `UniMessage.send` 发送的消息 (str 或 Message 或 UniMessage) 进行额外处理,默认情况下会返回原始消息。
该方法会调用多次,即对于多个 Extension前一个 Extension 的返回值会作为下一个 Extension 的输入。
由于需要保证输入与输出的类型一致,该方法内需要自行判断类型。
### before_catch
```python
def before_catch(self, name: str, annotation: type, default: Any) -> bool: ...
```
该方法用于响应函数中某个参数是否需要绑定到该 Extension 上。
### catch
```python
async def catch(self, interface: Interface) -> Any: ...
```
该方法用于注入经过 `before_catch` 确认的参数。其中 `Interface` 的定义为
```python
class Interface(Generic[TE]):
event: TE
state: T_State
name: str
annotation: Any
default: Any
```
## 补全会话
补全会话基于 [`半自动补全`](./command.md#半自动补全),用于指令参数缺失或参数错误时给予交互式提示,类似于 `got-reject`
```python
from nonebot_plugin_alconna import Alconna, Args, Field, At, on_alconna
alc = Alconna(
"添加教师",
Args["name", str, Field(completion=lambda: "请输入姓名")],
Args["phone", int, Field(completion=lambda: "请输入手机号")],
Args["at", [str, At], Field(completion=lambda: "请输入教师号")],
)
cmd = on_alconna(alc, comp_config={"lite": True}, skip_for_unmatch=False)
@cmd.handle()
async def handle(result: Arparma):
cmd.finish("添加成功")
```
此时,当用户输入 `添加教师` 时,会自动提示用户输入姓名,手机号和教师号,用户输入后会自动进入下一个提示:
<Messenger
msgs={[
{ position: "right", msg: "添加教师" },
{ position: "left", msg: "以下是建议的输入: \n- name: 请输入姓名" },
{ position: "right", msg: "foo" },
{ position: "left", msg: "以下是建议的输入: \n- phone: 请输入手机号" },
{ position: "right", msg: "12345" },
{ position: "left", msg: "以下是建议的输入: \n- at: 请输入教师号" },
{ position: "right", msg: "@me" },
{ position: "left", msg: "添加成功" },
]}
/>
补全会话配置如下:
```python
class CompConfig(TypedDict):
tab: NotRequired[str]
"""用于切换提示的指令的名称"""
enter: NotRequired[str]
"""用于输入提示的指令的名称"""
exit: NotRequired[str]
"""用于退出会话的指令的名称"""
timeout: NotRequired[int]
"""超时时间"""
hide_tabs: NotRequired[bool]
"""是否隐藏所有提示"""
hides: NotRequired[Set[Literal["tab", "enter", "exit"]]]
"""隐藏的指令"""
disables: NotRequired[Set[Literal["tab", "enter", "exit"]]]
"""禁用的指令"""
lite: NotRequired[bool]
"""是否使用简洁版本的补全会话(相当于同时配置 disables、hides、hide_tabs"""
block: NotRequired[bool]
"""进行补全会话时是否阻塞响应器"""
```

View File

@ -0,0 +1,121 @@
---
sidebar_position: 6
description: 快捷方式
---
# 快捷方式声明
针对 `Alconna` 编写对于入门开发者来说较为复杂的问题,本插件提供了一些快捷方式来简化开发者的工作。
## 装饰器构造器
本插件提供了一个 `funcommand` 装饰器, 其用于将一个接受任意参数, 返回 `str``Message``MessageSegment` 的函数转换为命令响应器:
```python
from nonebot_plugin_alconna import funcommand
@funcommand()
async def echo(msg: str):
return msg
```
其等同于:
```python
from arclet.alconna import Alconna, Args
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match
echo = on_alconna(Alconna("echo", Args["msg", str]))
@echo.handle()
async def echo_exit(msg: Match[str] = AlconnaMatch("msg")):
await echo.finish(msg.result)
```
相比于 `on_alconna` `funcommand` 增加了三个参数 `name`, `prefixes``description`
## 类 Koishi 构造器
本插件提供了一个 `Command` 构造器,其基于 `arclet.alconna.tools` 中的 `AlconnaString` 以类似 `Koishi` 中[注册命令](https://koishi.chat/zh-CN/guide/basic/command.html)的方式来构建一个 **AlconnaMatcher**
```python
from nonebot_plugin_alconna import Command, Arparma
book = (
Command("book", "测试")
.option("writer", "-w <id:int>")
.option("writer", "--anonymous", {"id": 0})
.usage("book [-w <id:int> | --anonymous]")
.shortcut("测试", {"args": ["--anonymous"]})
.build()
)
@book.handle()
async def _(arp: Arparma):
await book.send(str(arp.options))
```
甚至,你可以设置 `action` 来设定响应行为:
```python
book = (
Command("book", "测试")
.option("writer", "-w <id:int>")
.option("writer", "--anonymous", {"id": 0})
.usage("book [-w <id:int> | --anonymous]")
.shortcut("测试", {"args": ["--anonymous"]})
.action(lambda options: str(options)) # 会自动通过 bot.send 发送
.build()
)
```
### 参数类型
`Command` 的参数类型也如 `koishi` 一样,**必选参数** 用尖括号包裹,**可选参数** 用方括号包裹:
- `foo` 表示参数 `foo`, 类型为 Any
- `foo:int` 表示参数 `foo`, 类型为 int
- `foo:int=1` 表示参数 `foo`, 类型为 int, 默认值为 1
- `...foo` 表示[泛匹配参数](command.md#allparam)
- `foo:str+`, `foo:str*` 表示[变长参数](command.md#multivar-与-keywordvar) `foo`, 类型为 str
- `foo:+str`, `foo:text` 表示参数 `foo`, 类型为 str, 并且将包含空格 (即将变长参数的结果用空格合并)
特别的,针对类型部分,本插件拓展了如下内容:
- `foo:At`, `foo:Image`, ... 表示类型为[通用消息段](./uniseg/segment.md)
- `foo:select(Image).first` 表示获取子元素类型
- `foo:Dot(Image, 'url')` 表示类型为 `Image`,并且只获取 `url` 属性
### 从文件加载
`Command` 支持读取 `json``yaml` 文件来加载命令:
```yml title="book.yml"
command: book
help: 测试
options:
- name: writer
opt: "-w <id:int>"
- name: writer
opt: "--anonymous"
default:
id: 1
usage: book [-w <id:int> | --anonymous]
shortcuts:
- key: 测试
args: ["--anonymous"]
actions:
- params: ["options"]
code: |
return str(options)
```
```python title="加载"
from nonebot_plugin_alconna import command_from_yaml
book = command_from_yaml("book.yml")
```

View File

@ -0,0 +1,203 @@
# 通用消息组件
`uniseg` 模块属于 `nonebot-plugin-alconna` 的子插件。
通用消息组件内容较多,故分为了一个示例以及数个专题。
## 示例
### 导入
一般情况下,你只需要从 `nonebot_plugin_alconna.uniseg` 中导入 `UniMessage` 即可:
```python
from nonebot_plugin_alconna.uniseg import UniMessage
```
### 构建
你可以通过 `UniMessage` 上的快捷方法来链式构造消息:
```python
message = (
UniMessage.text("hello world")
.at("1234567890")
.image(url="https://example.com/image.png")
)
```
也可以通过导入通用消息段来构建消息:
```python
from nonebot_plugin_alconna import Text, At, Image, UniMessage
message = UniMessage(
[
Text("hello world"),
At("user", "1234567890"),
Image(url="https://example.com/image.png"),
]
)
```
更深入一点,比如你想要发送一条包含多个按钮的消息,你可以这样做:
```python
from nonebot_plugin_alconna import Button, UniMessage
message = (
UniMessage.text("hello world")
.keyboard(
Button("link1", url="https://example.com/1"),
Button("link2", url="https://example.com/2"),
Button("link3", url="https://example.com/3"),
row=3,
)
)
```
### 发送
你可以通过 `.send` 方法来发送消息:
```python
@matcher.handle()
async def _():
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
await message.send()
# 类似于 `matcher.finish`
await message.finish()
```
你可以通过参数来让消息 @ 发送者:
```python
@matcher.handle()
async def _():
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
await message.send(at_sender=True)
```
或者回复消息:
```python
@matcher.handle()
async def _():
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
await message.send(reply_to=True)
```
### 撤回,编辑,表态
你可以通过 `message_recall`, `message_edit``message_reaction` 方法来撤回,编辑和表态消息事件。
```python
from nonebot_plugin_alconna import message_recall, message_edit, message_reaction
@matcher.handle()
async def _():
await message_edit(UniMessage.text("hello world"))
await message_reaction("👍")
await message_recall()
```
你也可以对你自己发送的消息进行撤回,编辑和表态:
```python
@matcher.handle()
async def _():
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
receipt = await message.send()
await receipt.edit(UniMessage.text("hello world!"))
await receipt.reaction("👍")
await receipt.recall(delay=5) # 5秒后撤回
```
### 处理消息
通过依赖注入,你可以在事件处理器中获取通用消息:
```python
from nonebot_plugin_alconna import UniMsg
@matcher.handle()
async def _(msg: UniMsg):
...
```
然后你可以通过 `UniMessage` 的方法来处理消息.
例如,你想知道消息中是否包含图片,你可以这样做:
```python
ans1 = Image in message
ans2 = message.has(Image)
ans3 = message.only(Image)
```
或者,提取所有的图片:
```python
imgs_1 = message[Image]
imgs_2 = message.get(Image)
imgs_3 = message.include(Image)
imgs_4 = message.select(Image)
imgs_5 = message.filter(lambda x: x.type == "image")
imgs_6 = message.tranform({"image": True})
```
而后,如果你想提取出所有的图片链接,你可以这样做:
```python
urls = imgs.map(lambda x: x.url)
```
如果你想知道消息是否符合某个前缀,你可以这样做:
```python
@matcher.handle()
async def _(msg: UniMsg):
if msg.startswith("hello"):
await matcher.finish("hello world")
else:
await matcher.finish("not hello world")
```
或者你想接着去除掉前缀:
```python
@matcher.handle()
async def _(msg: UniMsg):
if msg.startswith("hello"):
msg = msg.removeprefix("hello")
await matcher.finish(msg)
else:
await matcher.finish("not hello world")
```
### 持久化
假设你在编写一个词库查询插件,你可以通过 `UniMessage.dump` 方法来将消息序列化为 JSON 格式:
```python
from nonebot_plugin_alconna import UniMsg
@matcher.handle()
async def _(msg: UniMsg):
data: list[dict] = msg.dump()
# 你可以将 data 存储到数据库或者 JSON 文件中
```
而后你可以通过 `UniMessage.load` 方法来将 JSON 格式的消息反序列化为 `UniMessage` 对象:
```python
from nonebot_plugin_alconna import UniMessage
@matcher.handle()
async def _():
data = [
{"type": "text", "text": "hello world"},
{"type": "image", "url": "https://example.com/image.png"},
]
message = UniMessage.load(data)
```

View File

@ -0,0 +1,4 @@
{
"label": "通用消息组件",
"position": 5
}

View File

@ -0,0 +1,518 @@
---
sidebar_position: 3
description: 消息序列
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
# 通用消息序列
`uniseg` 提供了一个类似于 `Message` 的 `UniMessage` 类型,其元素为[通用消息段](./segment.md)。
你可以用如下方式获取 `UniMessage`
<Tabs groupId="get_unimsg">
<TabItem value="depend" label="使用依赖注入">
通过提供的 `UniversalMessage` 或基于 [`Annotated` 支持](https://github.com/nonebot/nonebot2/pull/1832)的 `UniMsg` 依赖注入器来获取 `UniMessage`。
```python
from nonebot_plugin_alconna.uniseg import UniMsg, At, Text
matcher = on_xxx(...)
@matcher.handle()
async def _(msg: UniMsg):
text = msg[Text, 0]
print(text.text)
if msg.has(At):
ats = msg.get(At)
print(ats)
...
```
</TabItem>
<TabItem value="method" label="使用 UniMessage.generate">
注意,`generate` 方法在响应器以外的地方如果不传入 `event` 与 `bot` 则无法处理 reply。
```python
from nonebot import Message, EventMessage
from nonebot_plugin_alconna.uniseg import UniMessage
matcher = on_xxx(...)
@matcher.handle()
async def _(message: Message = EventMessage()):
msg = await UniMessage.generate(message=message)
msg1 = UniMessage.generate_without_reply(message=message)
```
</TabItem>
</Tabs>
## 发送消息
你还可以通过 `UniMessage` 的 `export` 与 `send` 方法来**跨平台发送消息**。
`UniMessage.export` 会通过传入的 `bot: Bot` 参数,或上下文中的 `Bot` 对象读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列:
```python
from nonebot import Bot, on_command
from nonebot_plugin_alconna.uniseg import Image, UniMessage
test = on_command("test")
@test.handle()
async def handle_test():
await test.send(await UniMessage(Image(path="path/to/img")).export())
```
除此之外 `UniMessage.send`, `.finish` 方法基于 `UniMessage.export` 并调用各适配器下的发送消息方法,返回一个 `Receipt` 对象,用于修改/撤回/表态消息:
```python
from nonebot import Bot, on_command
from nonebot_plugin_alconna.uniseg import UniMessage
test = on_command("test")
@test.handle()
async def handle():
receipt = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
await receipt.recall(delay=1)
```
`UniMessage.send` 的定义如下:
```python
async def send(
self,
target: Event | Target | None = None,
bot: Bot | None = None,
fallback: bool | FallbackStrategy = FallbackStrategy.rollback,
at_sender: str | bool = False,
reply_to: str | bool | Reply | None = False,
**kwargs: Any,
) -> Receipt:
...
```
- `target`: 发送目标,支持事件和[发送对象](./utils.mdx#发送对象),不传入时会尝试从响应器上下文中获取。
- `bot`: 发送消息使用的 Bot 对象,若不传入则会尝试从响应器上下文中获取。
- `fallback`: [回退策略](#回退策略)。
- `at_sender`: 是否提醒发送者,默认为 `False`。当类型为 `str` 时,表示指定用户的 id。
- `reply_to`: 是否回复消息,默认为 `False`。
- `str` 表示消息 id。
- `bool` 表示是否回复当前消息。此时 `target` 不能是[发送对象](./utils.mdx#发送对象)。
- `Reply` 表示直接使用回复元素。
- `**kwargs`: 各 `Bot.send` 的特定参数。
而在 `AlconnaMatcher` 下,`got`, `send`, `reject` 等可以发送消息的方法皆支持使用 `UniMessage`,不需要手动调用 export 方法:
```python
from arclet.alconna import Alconna, Args
from nonebot_plugin_alconna import Match, AlconnaMatcher, on_alconna
from nonebot_plugin_alconna.uniseg import At, UniMessage
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
@test_cmd.handle()
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
if target.available:
matcher.set_path_arg("target", target.result)
@test_cmd.got_path("target", prompt="请输入目标")
async def tt(target: At):
await test_cmd.send(UniMessage([target, "\ndone."]))
```
### 回退策略
`send` 方法的 `fallback` 参数用于指定回退策略(即当前适配器不支持的消息段如何处理):
- `FallbackStrategy.ignore`: 忽略未转换的消息段
- `FallbackStrategy.to_text`: 将未转换的消息段转为文本元素
- `FallbackStrategy.rollback`: 从未转换消息段的子元素中提取可能的可发送消息段
- `FallbackStrategy.forbid`: 抛出异常
- `FallbackStrategy.auto`: 插件自动选择策略
另外 `fallback` 传入 `bool` 时,`True` 等价于 `FallbackStrategy.auto``False` 等价于 `FallbackStrategy.forbid`。
### 主动发送消息
`UniMessage.send` 也可以用于主动发送消息:
```python
from nonebot_plugin_alconna.uniseg import UniMessage, Target, SupportScope
from nonebot import get_driver
driver = get_driver()
@driver.on_startup
async def on_startup():
target = Target("xxxx", scope=SupportScope.qq_client)
await UniMessage("Hello!").send(target=target)
```
:::caution
在响应器以外的地方,除非启用了 `alconna_apply_fetch_targets` 配置项,否则 `bot` 参数必须手动传入。
:::
### Receipt 对象
`send` 方法返回的 `Receipt` 对象可以用于修改/撤回/表态消息:
```python
async def handle():
receipt = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
await receipt.recall(delay=1)
recept1 = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
await recept1.edit("world!")
```
`Receipt` 对象拥有以下方法:
- `recallable`: 表明是否可以撤回
- `recall`: 撤回消息
- `editable`: 表明是否可以修改
- `edit`: 修改消息
- `reactionable`: 表明是否可以表态
- `reaction`: 表态消息
- `get_reply`: 生成对已经发送的消息的回复元素
- `send`, `finish`: 发送消息
- `reply`: 回复已经发送的消息
## 构造
如同 `Message`, `UniMessage` 可以传入单个字符串/消息段,或可迭代的字符串/消息段:
```python
from nonebot_plugin_alconna.uniseg import UniMessage, At
msg = UniMessage("Hello")
msg1 = UniMessage(At("user", "124"))
msg2 = UniMessage(["Hello", At("user", "124")])
```
`UniMessage` 上同时存在便捷方法,令其可以链式地添加消息段:
```python
from nonebot_plugin_alconna.uniseg import UniMessage, At, Image
msg = UniMessage.text("Hello").at("124").image(path="/path/to/img")
assert msg == UniMessage(
["Hello", At("user", "124"), Image(path="/path/to/img")]
)
```
### 使用消息模板
`UniMessage.template` 同样类似于 `Message.template`,可以用于格式化消息,大体用法参考 [消息模板](../../../tutorial/message#使用消息模板)。
这里额外说明 `UniMessage.template` 的拓展控制符
相比 `Message`UniMessage 对于 `{:XXX}` 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
以 At(...) 为例:
```python title=使用通用消息段的拓展控制符
>>> from nonebot_plugin_alconna.uniseg import UniMessage
>>> UniMessage.template("{:At(user, target)}").format(target="123")
UniMessage(At("user", "123"))
>>> UniMessage.template("{:At(type=user, target=id)}").format(id="123")
UniMessage(At("user", "123"))
>>> UniMessage.template("{:At(type=user, target=123)}").format()
UniMessage(At("user", "123"))
```
而在 `AlconnaMatcher` 中,`{:XXX}` 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
from arclet.alconna import Alconna, Args
from nonebot_plugin_alconna import At, Match, UniMessage, AlconnaMatcher, on_alconna
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
@test_cmd.handle()
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
if target.available:
matcher.set_path_arg("target", target.result)
@test_cmd.got_path(
"target",
prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请确认目标")
)
async def tt():
await test_cmd.send(
UniMessage.template("{:At(user, $event.get_user_id())} 已确认目标为 {target}")
)
```
另外也有 `$message_id` 与 `$target` 两个特殊值。
:::tip
注意到上述代码中的 `{target}` 了吗?
在 `AlconnaMatcher` 中,`UniMessage.template` 的格式化方法会自动将 `Arparma.all_matched_args`、 `state` 中的变量传入到 `format` 方法中,因此你可以直接使用上述变量。
:::
### 拼接消息
`str`、`UniMessage`、`Segment` 对象之间可以直接相加,相加均会返回一个新的 `UniMessage` 对象:
```python
# 消息序列与消息段相加
UniMessage("text") + Text("text")
# 消息序列与字符串相加
UniMessage([Text("text")]) + "text"
# 消息序列与消息序列相加
UniMessage("text") + UniMessage([Text("text")])
# 字符串与消息序列相加
"text" + UniMessage([Text("text")])
# 消息段与消息段相加
Text("text") + Text("text")
# 消息段与字符串相加
Text("text") + "text"
# 消息段与消息序列相加
Text("text") + UniMessage([Text("text")])
# 字符串与消息段相加
"text" + Text("text")
```
如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加:
```python
msg = UniMessage([Text("text")])
# 自加
msg += "text"
msg += Text("text")
msg += UniMessage([Text("text")])
# 附加
msg.append(Text("text"))
# 扩展
msg.extend([Text("text")])
```
## 操作
### 检查消息段
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
```python
# 是否存在消息段
At("user", "1234") in message
# 是否存在指定类型的消息段
At in message
```
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段:
```python
# 是否都为 "test"
message.only("test")
# 是否仅包含指定类型的消息段
message.only(Text)
```
### 获取消息纯文本
类似于 `Message.extract_plain_text()`,用于获取通用消息的纯文本:
```python
# 提取消息纯文本字符串
assert UniMessage(
[At("user", "1234"), "text"]
).extract_plain_text() == "text"
```
### 遍历
通用消息序列继承自 `List[Segment]` ,因此可以使用 `for` 循环遍历消息段:
```python
for segment in message: # type: Segment
...
```
### 过滤、索引与切片
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片:
```python
message = UniMessage(
[
Reply(...),
"text1",
At("user", "1234"),
"text2"
]
)
# 索引
message[0] == Reply(...)
# 切片
message[0:2] == UniMessage([Reply(...), Text("text1")])
# 类型过滤
message[At] == Message([At("user", "1234")])
# 类型索引
message[At, 0] == At("user", "1234")
# 类型切片
message[Text, 0:2] == UniMessage([Text("text1"), Text("text2")])
```
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤:
```python
message.include(Text, At)
message.exclude(Reply)
```
或者使用 `filter` 方法:
```python
message.filter(lambda x: isinstance(x, At) and x.flag == "user") # 仅保留 At("user", xxx) 的消息段
```
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段:
```python
# 指定类型首个消息段索引
message.index(Text) == 1
# 指定类型消息段数量
message.count(Text) == 2
```
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段:
```python
# 获取指定类型指定个数的消息段
message.get(Text, 1) == UniMessage([Text("test1")])
```
### 嵌套提取
消息序列的 `select` 方法可以递归地从消息中选择指定类型的消息段:
```python
message = UniMessage(
[
Text("text1"),
Image(url="url1")(
Text("text2"),
)
]
)
assert message.select(Text) == UniMessage(
[
Text("text1"),
Text("text2")
]
)
```
### 转换
消息序列的 `map` 方法可以简单地将消息段转换为指定类型的数据:
```python
# 转换消息段为另一类型的消息段,此时返回结果仍是 UniMessage
message.map(lambda x: Text(x.target)) # 转换为 Text 消息段
# 转换消息段为另一类型的数据,此时返回结果为 list[T]
message.map(lambda x: x.target) # 转换为 list[str]
```
在此之上,消息序列还提供了 `transform` 和 `transform_async` 方法,允许你传入转换规则,将消息段转换为另一类型的消息段,并返回一个新的消息序列:
```python
rule = {
"text": True,
"at": lambda attrs, children: Text(attrs["target"])
}
message.transform(rule)
```
转换规则的类型一般为 `dict[str, Transformer]`,以消息元素类型的名称为键,定义方式如下:
```typescript
type Fragment = Segment | Segment[];
type Render<T> = (attrs: dict, children: Segment[]) => T;
type Transformer = boolean | Fragment | Render<boolean | Fragment>;
```
### 字符串操作
类似于 `str`,消息序列可以通过如下方法来操作消息内的文本部分:
- `split`,
- `replace`,
- `startwith`, `endswith`,
- `removeprefix`, `removesuffix`,
- `strip`, `lstrip`, `rstrip`,
```python
msg = UniMessage.text("foo bar").at("1234").text("baz qux")
# 分割,返回分割结果,类型为 list[UniMessage]
parts = msg.split(" ")
# 替换,返回替换结果,类型为 UniMessage。新文本可以用 str 或 Text 来替换
new_msg = msg.replace("ba", "baaa")
# 前缀/后缀检查
msg.startswith("foo") # True
msg.endswith("qux") # True
# 去除前缀/后缀
msg1 = msg.removeprefix("foo") # UniMessage([Text(" bar"), At("user", "1234"), Text("baz qux")])
msg2 = msg.removesuffix("qux") # UniMessage([Text("foo bar"), At("user", "1234"), Text("baz ")])
# 去除空格
msg1 = msg1.lstrip() # UniMessage([Text("bar"), At("user", "1234"), Text("baz qux")])
msg2 = msg2.rstrip() # UniMessage([Text("foo bar"), At("user", "1234"), Text("baz")])
```
## 持久化
特别的,`UniMessage` 还支持消息持久化,具体来说为 `dump` 与 `load` 方法:
```python
msg = UniMessage.text("Hello").image(url="url")
data = msg.dump() # [{"type": "text", "text": "Hello"}, {"type": "image", "url": "url"}]
assert UniMessage.load(data) == msg
```
### dump
`dump` 方法的定义如下:
```python
def dump(self, media_save_dir: str | Path | bool | None = None, json: bool = False) -> str | list[dict[str, Any]]: ...
```
其中,`media_save_dir` 用于指定持久化的媒体文件存储目录:
- 若 `media_save_dir` 为 str 或 Path则会将媒体文件保存到指定目录下。
- 若 `media_save_dir` 为 False则不会保存媒体文件。
- 若 `media_save_dir` 为 True则会将文件数据转为 base64 编码。
- 若不指定 `media_save_dir`,则会尝试导入 [`nonebot_plugin_localstore`](../../data-storing.md) 并使用其提供的路径。否则 (即 `localstore` 未安装),将会尝试使用当前工作目录。
### load
`load` 方法的定义如下:
```python
@classmethod
def load(cls, data: str | list[dict[str, Any]]) -> UniMessage: ...
```
其中 `data` 应符合 JSON 格式。

View File

@ -0,0 +1,222 @@
---
sidebar_position: 2
description: 消息段
---
# 通用消息段
通用消息段是对各适配器中的消息段的抽象总结。其可用于 Alconna 命令的参数定义,也可用于消息的构建和解析。
```python
from nonebot_plugin_alconna import Alconna, Args, Image, on_alconna
meme = on_alconna(Alconna("make_meme", Args["name", str]["img", Image]))
@meme.handle()
async def _(img: Image):
...
```
## 模型定义
> **注意**: 本节的内容经过简化。实际情况以源码为准。
```python
class Segment:
"""基类标注"""
@property
def type(self) -> str: ...
@property
def data(self) -> [str, Any]: ...
@property
def children(self) -> list["Segment"]: ...
class Text(Segment):
"""Text对象, 表示一类文本元素"""
text: str
styles: dict[tuple[int, int], list[str]]
def cover(self, text: str): ...
def mark(self, start: Optional[int] = None, end: Optional[int] = None, *styles: str): ...
class At(Segment):
"""At对象, 表示一类提醒某用户的元素"""
flag: Literal["user", "role", "channel"]
target: str
display: Optional[str]
class AtAll(Segment):
"""AtAll对象, 表示一类提醒所有人的元素"""
here: bool
class Emoji(Segment):
"""Emoji对象, 表示一类表情元素"""
id: str
name: Optional[str]
class Media(Segment):
id: Optional[str]
url: Optional[str]
path: Optional[Union[str, Path]]
raw: Optional[Union[bytes, BytesIO]]
mimetype: Optional[str]
name: str
to_url: ClassVar[Optional[MediaToUrl]]
class Image(Media):
"""Image对象, 表示一类图片元素"""
width: Optional[int]
height: Optional[int]
class Audio(Media):
"""Audio对象, 表示一类音频元素"""
duration: Optional[float]
class Voice(Media):
"""Voice对象, 表示一类语音元素"""
duration: Optional[float]
class Video(Media):
"""Video对象, 表示一类视频元素"""
thumbnail: Optional[Image]
duration: Optional[float]
class File(Media):
"""File对象, 表示一类文件元素"""
class Reply(Segment):
"""Reply对象表示一类回复消息"""
id: str
"""此处不一定是消息ID可能是其他ID如消息序号等"""
msg: Optional[Union[Message, str]]
origin: Optional[Any]
class Reference(Segment):
"""Reference对象表示一类引用消息。转发消息 (Forward) 也属于此类"""
id: Optional[str]
"""此处不一定是消息ID可能是其他ID如消息序号等"""
children: List[Union[RefNode, CustomNode]]
class Hyper(Segment):
"""Hyper对象表示一类超级消息。如卡片消息、ark消息、小程序等"""
format: Literal["xml", "json"]
raw: Optional[str]
content: Optional[Union[dict, list]]
class Reference(Segment):
"""Reference对象表示一类引用消息。转发消息 (Forward) 也属于此类"""
id: Optional[str]
nodes: Sequence[Union[RefNode, CustomNode]]
class Button(Segment):
"""Button对象表示一类按钮消息"""
flag: Literal["action", "link", "input", "enter"]
"""
- 点击 action 类型的按钮时会触发一个关于 按钮回调 事件,该事件的 button 资源会包含上述 id
- 点击 link 类型的按钮时会打开一个链接或者小程序,该链接的地址为 `url`
- 点击 input 类型的按钮时会在用户的输入框中填充 `text`
- 点击 enter 类型的按钮时会直接发送 `text`
"""
label: Union[str, Text]
"""按钮上的文字"""
clicked_label: Optional[str]
"""点击后按钮上的文字"""
id: Optional[str]
url: Optional[str]
text: Optional[str]
style: Optional[str]
"""
仅建议使用下列值primary, secondary, success, warning, danger, info, link, grey, blue
此处规定 `grey` 与 `secondary` 等同, `blue` 与 `primary` 等同
"""
permission: Union[Literal["admin", "all"], list[At]] = "all"
"""
- admin: 仅管理者可操作
- all: 所有人可操作
- list[At]: 指定用户/身份组可操作
"""
class Keyboard(Segment):
"""Keyboard对象表示一行按钮元素"""
id: Optional[str]
"""此处一般用来表示模板id特殊情况下可能表示例如 bot_appid 等"""
buttons: Optional[list[Button]]
row: Optional[int]
"""当消息中只写有一个 Keyboard 时可根据此参数约定按钮组的列数"""
class Other(Segment):
"""其他 Segment"""
origin: MessageSegment
class I18n(Segment):
"""特殊的 Segment用于 i18n 消息"""
item_or_scope: Union[LangItem, str]
type_: Optional[str] = None
def tp(self) -> UniMessageTemplate: ...
```
:::tip
或许你注意到了 `Segment` 上有一个 `children` 属性。
这是因为在 [`Satori`](https://satori.js.org/zh-CN/) 协议的规定下,一类元素可以用其子元素来代表一类兼容性消息
例如qq 的商场表情在某些平台上可以用图片代替)。
为此,本插件提供了 `select` 方法来表达 "命令中获取子元素" 的方法:
```python
from nonebot_plugin_alconna import Args, Image, Alconna, select
from nonebot_plugin_alconna.builtins.uniseg.market_face import MarketFace
# 表示这个指令需要的图片会在目标元素下进行搜索,将所有符合 Image 的元素选出来并将第一个作为结果
alc1 = Alconna("make_meme", Args["name", str]["img", select(Image).first]) # 也可以使用 select(Image).nth(0)
# 表示这个指令需要的图片要么直接是 Image 要么是在 MarketFace 元素内的 Image
alc2 = Alconna("make_meme", Args["name", str]["img", [Image, select(Image).from_(MarketFace)]])
```
也可以参考通用消息的 [`嵌套提取`](./message.mdx#嵌套提取)
:::
## 自定义消息段
`uniseg` 提供了部分方法来允许用户自定义 Segment 的序列化和反序列化:
```python
from dataclasses import dataclass
from nonebot.adapters import Bot
from nonebot.adapters import MessageSegment as BaseMessageSegment
from nonebot.adapters.satori import Custom, Message, MessageSegment
from nonebot_plugin_alconna.uniseg.builder import MessageBuilder
from nonebot_plugin_alconna.uniseg.exporter import MessageExporter
from nonebot_plugin_alconna.uniseg import Segment, custom_handler, custom_register
@dataclass
class MarketFace(Segment):
tabId: str
faceId: str
key: str
@custom_register(MarketFace, "chronocat:marketface")
def mfbuild(builder: MessageBuilder, seg: BaseMessageSegment):
if not isinstance(seg, Custom):
raise ValueError("MarketFace can only be built from Satori Message")
return MarketFace(**seg.data)(*builder.generate(seg.children))
@custom_handler(MarketFace)
async def mfexport(exporter: MessageExporter, seg: MarketFace, bot: Bot, fallback: bool):
if exporter.get_message_type() is Message:
return MessageSegment("chronocat:marketface", seg.data)(await exporter.export(seg.children, bot, fallback))
```
具体而言,你可以使用 `custom_register` 来增加一个从 MessageSegment 到 Segment 的处理方法;使用 `custom_handler` 来增加一个从 Segment 到 MessageSegment 的处理方法。

View File

@ -0,0 +1,282 @@
---
sidebar_position: 4
description: 辅助模型
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
# 辅助功能
`uniseg` 模块同时提供了多种方法以通用消息操作。
:::note
这些方法中与 `event`, `bot` 相关的参数都会尝试从上下文中获取对象。
:::
## 消息事件 ID
消息事件 ID 是用来标识当前消息事件的唯一 ID通常用于回复/撤回/编辑/表态当前消息。
<Tabs groupId="get_msgid">
<TabItem value="depend" label="使用依赖注入">
通过提供的 `MessageId` 或 `MsgId` 依赖注入器来获取消息事件 id。
```python
from nonebot_plugin_alconna.uniseg import MsgId
matcher = on_xxx(...)
@matcher.handle()
asycn def _(msg_id: MsgId):
...
```
</TabItem>
<TabItem value="method" label="使用获取函数">
```python
from nonebot import Event, Bot
from nonebot_plugin_alconna.uniseg import get_message_id
matcher = on_xxx(...)
@matcher.handle()
asycn def _(bot: Bot, event: Event):
msg_id: str = get_message_id(event, bot)
```
</TabItem>
</Tabs>
:::caution
该方法获取的消息事件 ID 不推荐直接用于各适配器的 API 调用中,可能会操作失败。
:::
## 发送对象
消息发送对象是用来描述当前消息事件的可发送对象或者主动发送消息时的目标对象,它包含了以下属性:
```python
class Target:
id: str
"""目标id若为群聊则为 group_id 或者 channel_id若为私聊则为 user_id"""
parent_id: str
"""父级id若为频道则为 guild_id其他情况下可能为空字符串例如 Feishu 下可作为部门 id"""
channel: bool
"""是否为频道,仅当目标平台符合频道概念时"""
private: bool
"""是否为私聊"""
source: str
"""可能的事件id"""
self_id: str | None
"""机器人id若为 None 则 Bot 对象会随机选择"""
selector: Callable[[Bot], Awaitable[bool]] | None
"""选择器,用于在多个 Bot 对象中选择特定 Bot"""
extra: dict[str, Any]
"""额外信息,用于适配器扩展"""
```
<Tabs groupId="get_target">
<TabItem value="depend" label="使用依赖注入">
通过提供的 `MessageTarget` 或 `MsgTarget` 依赖注入器来获取消息发送对象。
```python
from nonebot_plugin_alconna.uniseg import MsgTarget
matcher = on_xxx(...)
@matcher.handle()
asycn def _(target: MsgTarget):
...
```
</TabItem>
<TabItem value="method" label="使用获取函数">
```python
from nonebot import Event, Bot
from nonebot_plugin_alconna.uniseg import Target, get_target
matcher = on_xxx(...)
@matcher.handle()
asycn def _(bot: Bot, event: Event):
target: Target = get_target(event, bot)
```
</TabItem>
</Tabs>
主动构造一个发送对象时,则需要如下参数:
- `id`: 目标ID若为群聊则为 `group_id` 或者 `channel_id`,若为私聊则为 `user_id`
- `parent_id`: 父级ID若为频道则为 `guild_id`,其他情况下可能为空字符串(例如 Feishu 下可作为部门 id
- `channel`: 是否为频道,仅当目标平台符合频道概念时
- `private`: 是否为私聊
- `source`: 可能的事件ID
- `self_id`: 机器人id若为 None 则 Bot 对象会随机选择
- `selector`: 选择器,用于在多个 Bot 对象中选择特定 Bot
- `scope`: 平台范围,表示当前发送对象的平台类别
- `adapter`: 适配器名称,若为 None 则需要明确指定 Bot 对象
- `platform`: 平台名称,仅当目标适配器存在多个平台时使用
- `extra`: 额外信息,用于适配器扩展
通过 `Target` 对象,我们可以在 `UniMessage.send` 中指定发送对象:
```python
from nonebot_plugin_alconna.uniseg import UniMessage, MsgTarget, Target, SupportScope
matcher = on_xxx(...)
@matcher.handle()
async def _(target: MsgTarget):
# 将消息发送给当前事件的发送者
await UniMessage("Hello!").send(target=target)
# 主动发送消息给群号为 12345 的 QQ 群聊
target1 = Target("12345", scope=SupportScope.qq_client)
await UniMessage("Hello!").send(target=target1)
```
### 选择器
一般来说,主动发送消息时,`UniMessage.send` 或 `Target.self_id` 应指定一个 `Bot` 对象。但是这样会加重开发者的负担。
因此,我们提供了选择器来帮助开发者选择一个 `Bot` 对象。当然,这并非说明一定需要传入 `selector` 参数。
事实上,构造 `Target` 对象时,`self_id`, `scope`, `adapter` 和 `platform` 都会参与到 `selector` 的构造中。
::tip
你其实可以使用 `Target` 来帮你筛选 `Bot` 对象:
```python
async def _():
target = Target("12345", scope=SupportScope.qq_client)
bot = await target.select()
```
:::
若配置了 [`alconna_apply_fetch_targets`](../config.md#alconna_apply_fetch_targets) 选项,则在启动时会主动拉取一次发送对象列表。即对于
某一主动构造的 `Target` 对象,插件将其与拉取下来的众多发送对象进行匹配,并选择第一个符合条件的发送对象,以选择对应的 Bot 对象。
## 撤回消息
通过 `message_recall` 方法来撤回消息事件。
```python
from nonebot_plugin_alconna.uniseg import message_recall
matcher = on_xxx(...)
@matcher.handle()
async def _(msg_id: MsgId):
await message_recall(msg_id)
```
`message_recall` 方法的参数如下:
```python
async def message_recall(
message_id: str | None = None,
event: Event | None = None,
bot: Bot | None = None,
adapter: str | None = None
): ...
```
当 `message_id` 为 `None` 时,插件会尝试从 `event` 中获取消息事件 ID。
## 编辑消息
通过 `message_edit` 方法来编辑消息事件。
```python
from nonebot_plugin_alconna.uniseg import UniMessage, message_edit
matcher = on_xxx(...)
@matcher.handle()
async def _():
await message_edit(UniMessage.text("1234"))
```
`message_edit` 方法的参数如下:
```python
async def message_edit(
msg: UniMessage,
message_id: str | None = None,
event: Event | None = None,
bot: Bot | None = None,
adapter: str | None = None,
): ...
```
当 `message_id` 为 `None` 时,插件会尝试从 `event` 中获取消息事件 ID。
## 表态消息
:::caution
该方法属于实验性功能。其接口可能会在未来的版本中发生变化。
:::
通过 `message_reaction` 方法来表态消息事件。
```python
from nonebot_plugin_alconna.uniseg import message_reaction
matcher = on_xxx(...)
@matcher.handle()
async def _():
await message_reaction("👍")
```
`message_reaction` 方法的参数如下:
```python
async def message_reaction(
reaction: str | Emoji,
message_id: str | None = None,
event: Event | None = None,
bot: Bot | None = None,
adapter: str | None = None,
delete: bool = False,
): ...
```
当 `message_id` 为 `None` 时,插件会尝试从 `event` 中获取消息事件 ID。
`delete` 参数表示是否删除**自己的**表态消息,默认为 `False`。
## 响应规则
`uniseg` 模块提供了两个响应规则:
- `at_in`: 是否在消息中 @ 了指定的用户
- `at_me`: 是否在消息中 @ 了机器人
相较于 NoneBot 内置的 `to_me` 规则,`at_me` 规则只会在消息中 @ 机器人时触发。
```python
from nonebot_plugin_alconna.uniseg import at_me
matcher = on_xxx(..., rule=at_me())
```