Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: 更新最佳实践的 Alconna 部分 #2686

Merged
merged 4 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion website/docs/best-practice/alconna/matcher.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ matcher = on_alconna(

```python
from nonebot_plugin_alconna import Match, on_alconna
from nonebot_plugin_alconna.adapters.discord import DiscordSlashExtension
from nonebot_plugin_alconna.builtins.plugins.discord import DiscordSlashExtension


alc = Alconna(
Expand All @@ -509,6 +509,12 @@ async def remove(plugin: Match[str], time: Match[int]):
await matcher.finish(f"removed {plugin.result} with {time.result if time.available else -1}")
```

目前插件提供了 4 个内置的 `Extension`,它们在 `nonebot_plugin_alconna.builtins.extensions` 下:
- `ReplyRecordExtension`: 将消息事件中的回复暂存在 extension 中,使得解析用的消息不带回复信息,同时可以在后续的处理中获取回复信息。
- `DiscordSlashExtension`: 将 Alconna 的命令自动转换为 Discord 的 Slash Command,并将 Slash Command 的交互事件转换为消息交给 Alconna 处理。
- `MarkdownOutputExtension`: 将 Alconna 的自动输出转换为 Markdown 格式
- `TelegramSlashExtension`: 将 Alconna 的命令注册在 Telegram 上以获得提示。

:::tip

全局的 Extension 可延迟加载 (即若有全局拓展加载于部分 AlconnaMatcher 之后,这部分响应器会被追加拓展)
Expand Down Expand Up @@ -572,3 +578,26 @@ class CompConfig(TypedDict):
lite: NotRequired[bool]
"""是否使用简洁版本的补全会话(相当于同时配置 disables、hides、hide_tabs)"""
```

## 内置插件

类似于 Nonebot 本身提供的内置插件,`nonebot_plugin_alconna` 提供了两个内置插件:`echo` 和 `help`。

你可以用本插件的 `load_builtin_plugin(s)` 来加载它们:

```python
from nonebot_plugin_alconna import load_builtin_plugins

load_builtin_plugins("echo", "help")
```

其中 `help` 仅能列出所有 Alconna 指令。

<Messenger
msgs={[
{ position: "right", msg: "/帮助" },
{ position: "left", msg: "# 当前可用的命令有:\n 0 /echo : echo 指令\n 1 /help : 显示所有命令帮助\n# 输入'命令名 -h|--help' 查看特定命令的语法" },
{ position: "right", msg: "/echo [图片]" },
{ position: "left", msg: "[图片]" },
]}
/>
75 changes: 53 additions & 22 deletions website/docs/best-practice/alconna/uniseg.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import TabItem from "@theme/TabItem";
```python
class Segment:
"""基类标注"""
children: List["Segment"]

class Text(Segment):
"""Text对象, 表示一类文本元素"""
Expand Down Expand Up @@ -81,7 +82,7 @@ class Reference(Segment):
"""Reference对象,表示一类引用消息。转发消息 (Forward) 也属于此类"""
id: Optional[str]
"""此处不一定是消息ID,可能是其他ID,如消息序号等"""
content: Optional[Union[Message, str, List[Union[RefNode, CustomNode]]]]
children: List[Union[RefNode, CustomNode]]

class Hyper(Segment):
"""Hyper对象,表示一类超级消息。如卡片消息、ark消息、小程序等"""
Expand All @@ -93,35 +94,26 @@ class Other(Segment):
"""其他 Segment"""
origin: MessageSegment

class Custom(Segment, abc.ABC):
"""Custom对象,表示一类自定义消息"""

mstype: str
content: Any

@abc.abstractmethod
def export(self, msg_type: Type[TM]) -> MessageSegment[TM]: ...
```

此类消息段通过 `UniMessage.export` 可以转为特定的 `MessageSegment`

:::tips

`Custom` 是一个抽象类,你可以通过继承 `Custom` 来实现自定义消息段:
或许你注意到了 `Segment` 上有一个 `children` 属性。

```python
from nonebot_plugin_alconna.uniseg import Custom, custom_register
from nonebot.adapters.qq import Message as QQMessage, MessageSegment as QQMessageSegment
这是因为在 [`Satori`](https://satori.js.org/zh-CN/) 协议的规定下,一类元素可以用其子元素来代表一类兼容性消息
(例如,qq 的商场表情在某些平台上可以用图片代替)。

为此,本插件提供了两种方式来表达 "获取子元素" 的方法:

class Markdown(Custom):
```python
from nonebot_plugin_alconna.builtins.uniseg.chronocat import MarketFace
from nonebot_plugin_alconna import Args, Image, Alconna, select, select_first

def export(self, msg_type: Type[TM]) -> MessageSegment[TM]:
if msg_type is QQMessage:
return QQMessageSegment.markdown(self.content)
# 表示这个指令需要的图片要么直接是 Image 要么是在 MarketFace 元素内的 Image
alc1 = Alconna("make_meme", Args["img", [Image, Image.from_(MarketFace)]])

@custom_register(Markdown, "markdown")
def markdown(seg: QQMessageSegment) -> Markdown:
return Markdown("markdown", content=seg.data["content"])
# 表示这个指令需要的图片会在目标元素下进行搜索,将所有符合 Image 的元素选出来并将第一个作为结果
alc2 = Alconna("make_meme", Args["img", select(Image, index=0)]) # 也可以使用 select_first(Image)
```

:::
Expand Down Expand Up @@ -557,3 +549,42 @@ async def on_startup():
target = Target("xxxx", scope=SupportScope.qq_client)
await UniMessage("Hello!").send(target=target)
```

## 自定义消息段

`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 的处理方法。
Loading