mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-09-05 19:46:47 +00:00
🔖 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
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:
@ -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)
|
||||
```
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "通用消息组件",
|
||||
"position": 5
|
||||
}
|
@ -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 格式。
|
@ -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 的处理方法。
|
@ -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())
|
||||
```
|
Reference in New Issue
Block a user