diff --git a/website/docs/best-practice/alconna/matcher.mdx b/website/docs/best-practice/alconna/matcher.mdx index 186e681e..d8656aa6 100644 --- a/website/docs/best-practice/alconna/matcher.mdx +++ b/website/docs/best-practice/alconna/matcher.mdx @@ -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( @@ -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 之后,这部分响应器会被追加拓展) @@ -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 指令。 + + diff --git a/website/docs/best-practice/alconna/uniseg.mdx b/website/docs/best-practice/alconna/uniseg.mdx index 9414714c..a660ede6 100644 --- a/website/docs/best-practice/alconna/uniseg.mdx +++ b/website/docs/best-practice/alconna/uniseg.mdx @@ -20,6 +20,7 @@ import TabItem from "@theme/TabItem"; ```python class Segment: """基类标注""" + children: List["Segment"] class Text(Segment): """Text对象, 表示一类文本元素""" @@ -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消息、小程序等""" @@ -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` 属性。 + +这是因为在 [`Satori`](https://satori.js.org/zh-CN/) 协议的规定下,一类元素可以用其子元素来代表一类兼容性消息 +(例如,qq 的商场表情在某些平台上可以用图片代替)。 + +为此,本插件提供了两种方式来表达 "获取子元素" 的方法: ```python -from nonebot_plugin_alconna.uniseg import Custom, custom_register -from nonebot.adapters.qq import Message as QQMessage, MessageSegment as QQMessageSegment +from nonebot_plugin_alconna.builtins.uniseg.chronocat import MarketFace +from nonebot_plugin_alconna import Args, Image, Alconna, select, select_first -class Markdown(Custom): +# 表示这个指令需要的图片要么直接是 Image 要么是在 MarketFace 元素内的 Image +alc1 = Alconna("make_meme", Args["img", [Image, Image.from_(MarketFace)]]) - def export(self, msg_type: Type[TM]) -> MessageSegment[TM]: - if msg_type is QQMessage: - return QQMessageSegment.markdown(self.content) - -@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) ``` ::: @@ -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 的处理方法。