跳转至

消息链: 基础🔗

为什么是消息链?🔗

QQ 消息并不只是纯文本, 也不只是单一类型的消息. 文本中可以夹杂着图片, At 某人等多种类型的消息.

mirai 为了处理富文本消息, 采用了消息链 (Message Chain)这一方式.

消息链可以看作是一系列元素 (Element) 构成的列表. 消息组件表示消息中的一部分, 比如纯文本 Plain, At 某人 At 等等.

关于可用的元素, 参看 API 文档.

消息链用法🔗

构造消息链🔗

构造消息链时, 建议采用 MessageChain 直接实例化.

支持使用以下方法构造.

message_chain = MessageChain([AtAll(), Plain("Hello World!")])
message_chain = MessageChain([AtAll(), "Hello World!"])
message_chain = MessageChain(AtAll(), "Hello World!")

消息链的字符串表示🔗

使用 message_chain.display 属性获取消息链的字符串表示.字符串表示的格式类似于手机 QQ 在通知栏消息中的格式, 例如图片会被转化为 [图片], 等等.

消息链持久化🔗

使用 message_chain.as_persistent_string()MessageChain.as_persistent_string() 可以尽量无损地持久化与恢复消息链, 使用 binary=False 可以不包括图片等多媒体元素的二进制数据.

提示

如果要持久化二进制数据, 可以先 await message_chain.download_binary().

遍历🔗

可以使用 for 循环遍历消息链中的消息组件.

for element in message_chain: ...

比较🔗

可以使用 == 运算符比较两个消息链是否相同.

another_msg_chain = MessageChain([AtAll(), Plain("Hello World!")])
assert message_chain == another_msg_chain

检查子链🔗

可以使用 in 运算检查消息链中:

  1. 是否有某个消息组件.
  2. 是否有某个类型的消息组件.
  3. 是否有某子字符串.
  4. 是否有某个消息链. (From 0.4.2 )
AtAll in message_chain

At(app.account) in message_chain

'Hello' in message_chain

MessageChain([AtAll(), "Hello World!"]) in message_chain

消息链的 has 方法和 in 等价.

你可以使用 only 方法检查消息链是否只有某些元素类型.

还可以使用 find_sub_chain 方法寻找可能的消息链子链起始点.

assert message_chain.find_sub_chain(MessageChain(["Hello"])) == [0]

索引与切片🔗

消息链对索引操作进行了增强.以元素类型为索引, 获取消息链中的全部该类型的消息组件.

assert message_chain[Plain] == [Plain("Hello World!")]

类型, 数量 为索引, 获取前 至多 多少个该类型的元素.

assert message_chain[Plain, 1] == [Plain("Hello World!")]

下标 为索引, 获取对应下标的元素.

assert message_chain[0] == Plain("Hello World!")

切片对象 为索引, 相当于调用 MessageChain(message_chain.content[slice]).

消息链的 get 方法和索引操作等价.

assert message_chain.get(Plain) == [Plain("Hello World!")]

消息链的 get 方法可指定第二个参数 count, 相当于以 类型, 数量 为索引.

assert message_chain.get(Plain, 1) == message_chain[Plain, 1]

获取元素🔗

MessageChain 对象上, 有以下几种获取元素的方式:

get_first(T_Element) 获取第一个类型为 T_Element 的元素. get(T_Element) 获取所有类型为 T_Element 的元素, 聚合为列表. get_one(T_Element, index) 获取第 index 个类型为 T_Element 的元素。 get(T_Element, count) 获取前 count 个类型为 T_element 的元素, 聚合为列表.

连接与复制🔗

可以用 + 连接两个消息链, 用 * 复制消息链.

assert MessageChain(['Hello World!']) + MessageChain(['Goodbye World!']) == MessageChain([Plain("Hello World!"), Plain("Goodbye World!")])
assert MessageChain(['Hello World!']) * 2 == MessageChain([Plain("Hello World!"), Plain("Hello World!")])

其他🔗

除此之外, 消息链还支持很多 list 拥有的操作, 比如 indexcount.

message_chain = MessageChain([AtAll(), "Hello World!"])
assert message_chain.index(Plain) == 0
assert message_chain.count(Plain) == 1

还有和 str 一脉相承的 startswith, endswith, removeprefix, removesuffix, replace 方法, 将在接下来讲到.

多媒体元素🔗

相信你在 docstring 与函数签名的辅助下, 能够很快掌握 Plain At AtAll 三种元素类型.

接下来将介绍继承自 MultimediaElement 的多媒体元素: Image FlashImage Voice.

实例化🔗

你可以通过以下方式自行实例化多媒体元素:

  • Mirai API HTTP 缓存的图片构造: 传入完整 id (不是 uuid)
  • 从网络图片构造: 传入 url
  • bytes 字节对象构造: 通过 data_bytes 传入 bytes 包装的二进制数据.
  • base64 字符串构造: 传入 base64 作为二进制存储.
  • 从本地文件构造: 传入 path 并以 当前工作目录 读入二进制数据.

提示: 传入的 path 会自动被立即提取出二进制数据. 所以不要想着先传 path 再写文件.

获取二进制🔗

你可以通过 get_bytes() 异步方法获取多媒体元素的二进制数据.

提示

通过 base64 存储的多媒体元素也可通过本方法取出二进制数据.

网络图片的二进制数据会在下载后被存储于 base64 属性内作为缓存.

图片类型转换🔗

可以通过对 FlashImageImage 实例 使用 to_image from_image to_flash_image from_flash_image 方法进行两种图片类型转换.

等价性比较🔗

多媒体元素之间的相等比较需要以下条件:

  • 类型相同 (也就是说 ImageFlashImage 必定不等)
  • 以下属性中任意一个相等
    • base64 (data_bytes)
    • uuid (剔除了 "/" "{}" 等用于区分图片类型的符号后得到)
    • url

转发消息相关🔗

警告

Graia Project 不对你随意构建转发消息造成的任何可能后果负责.

使用 Forward 并传入一个 ForwardNode 的列表即可构建.

ForwardNode 可以自定义单个消息的发出者名字与其 QQ 号, 通过传入 nametarget 参数实现.

同时, 直接向 target 传入 Friend Member 等对象可以从中提取出 name (Member 使用群名片, Friend 使用昵称) 而不用单独传入.

如果你只是想对 Ariadne 有个粗略的了解, 并着手开始编写自己的 QQ bot, 相信这些知识已经足够. 如果你想进一步挖掘 AriadneMessageChain 特性, 请往下看.

消息链: 进阶🔗

筛选元素🔗

使用 includeexclude 方法可以筛选消息链中的元素.

msg_chain = MessageChain.create("Hello", At(target=12345))
assert msg_chain.include(Plain) == MessageChain([Plain(text='Hello')])
assert msg_chain.exclude(Plain) == MessageChain([At(target=12345)])

分割🔗

使用 split 方法以切割消息链为 多个消息链.

raw_string 参数用于指示是否要保留 "空" 的文本元素.

msg_chain = MessageChain.create("Hello world!", At(target=12345))
assert msg_chain.split("world!", raw_string=True) == [MessageChain([Plain(text='Hello ')]), MessageChain([Plain(text=''), At(target=12345)])]
assert msg_chain.split("world!") == [MessageChain([Plain(text='Hello ')]), MessageChain([At(target=12345)])]

前缀与后缀操作🔗

与字符串对象一样, 消息链对象支持 startswith, endswith, removeprefix, removesuffix 四个方法.

注意

消息链在执行这些方法时 不会去掉其他元素.

msg_chain = MessageChain.create("Hello world!", At(target=12345))
assert msg_chain.removeprefix("Hello") == MessageChain([Plain(text=' world!'), At(target=12345)])
assert msg_chain.removesuffix("world!") == MessageChain([Plain(text='Hello world!'), At(target=12345)])
assert not msg_chain.endswith("world!")
提示

removeprefix 方法有一个额外的 skip_header 参数, 可以跳过 SourceQuote 进行操作 (并在最后放回消息链).

又及

你知道的, Python 在 3.9 以后才正式引入 removeprefixremovesuffix 方法......

不过 Ariadne 中的这两个方法并不需要 Python 3.9+

replace 方法🔗

MessageChainreplace 方法与 strreplace 方法有异曲同工之妙.

在其表面下, find_sub_chain 承担了大部分工作, 找出所有符合 old 的部分, 之后由简单的循环完成替换.

>>> MessageChain(["Hello World!Hello World!How are you?", At(1), "yo"]).replace(
...     MessageChain(["Hello World!"]),
...     MessageChain(["No!"])
... )
MessageChain([Plain("No!No!How are you?"), At(1), Plain("yo")])

提示

这对于 At 等元素也适用. 此外, replaceold, new 参数为 MessageChain, Iterable[Element], Element 中一种即可.

msg.replace(At(app.account), Plain("[bot]"))

join 方法🔗

MessageChainjoin 方法与 strjoin 方法大致相同.

接受一个内容为 MessageChain 的可迭代对象, 并用其自身拼接.

merge 参数决定是否自动帮你拼接消息链, 默认为是.

>>> MessageChain([" "]).join([MessageChain(["A"]), MessageChain(["B"]), MessageChain(["C"])])
MessageChain([Plain("A B C")])
>>> MessageChain([" "]).join([MessageChain(["A"]), MessageChain(["B"]), MessageChain(["C"])], merge=False)
MessageChain([Plain("A"), Plain(" "), Plain("B"), Plain(" "), Plain("C")])

元素安全性🔗

因为 MessageChain 是一个可变对象, 其底层的 Element 属性可以被修改, 所以自然可以这样做:

>>> chain = MessageChain([Plain("hello"), At(12345)])
>>> chain[1].target = 99999
>>> chain
MessageChain([Plain("hello"), At(99999)])

然后, 这样是 预期行为 :

>>> chain = MessageChain([Plain("Hello"), Plain("World"), At(12345)])
>>> merged = chain.merge()
>>> chain
MessageChain([Plain(text='HelloWorld'), At(target=12345)])
>>> merged[0].text = "test"
>>> chain
MessageChain([Plain(text='test'), At(target=12345)])
>>> chain = MessageChain([Plain("Hello"), Plain("World"), At(12345)])
>>> merged = chain.merge(copy=True)
>>> chain
MessageChain([Plain(text='HelloWorld'), At(target=12345)])
>>> merged[0].text = "test"
>>> chain
MessageChain([Plain(text='HelloWorld'), At(target=12345)])
>>> merged
MessageChain([Plain(text='test'), At(target=12345)])

原因很简单, AriadneMessageChain 是支持链式调用的, 所以 所有对消息链的操作都会返回一个消息链引用 .

0.5.1 起, 消息链的大部分修改操作都支持参数 copy (可能为仅关键字参数), copy = True 时会返回消息链的 副本 (相当于在 chain.copy() 上操作), 否则会返回自身的引用.

社区文档相关章节

总览

多媒体元素

文件发送

合并转发