OneBot 行为操作

相关知识

如果你不知道什么是“行为”和“行为操作”,建议先浏览:行为的相关知识

其他行为操作,和 send() 类似,都由对应的行为操作方法产生。关于这些方法和它们的参数,参考 OneBot v11 适配器的 API

它们的用法,与上一篇文章中的消息行为方法基本一致。可在事件处理方法中直接调用。

行为句柄

当直接使用 OneBot 的行为方法时,默认是尽快完成的。即不等待也不关心 OneBot 实现端是否成功完成了行为:

# 发送消息而不等待,也不关心是否成功
await adapter.send(...)

# 因此在某些情况下,以下的一系列行为操作可能是无序的:
await adapter.send("我想要这条消息先被看到")
await adapter.send("但是这条可能才是先被发出去的")
await adapter.send("也可能是这条")

# 而且有些行为需要响应数据
await adapter.get_group_list()
# 如何等待返回的数据呢?

此时就需要行为句柄和行为句柄组了。melobot 的所有行为操作,包括刚才提及的协议特定操作,或通用的操作(例如 send_text()),都会返回行为句柄。

melobot 是支持多源输出的。一个行为操作在通过行为操作函数创建后,会自动发送给匹配的协议适配器(一类或多类,当然同一协议只能有一个适配器),随后适配器会根据已有的输出源,以及输出源过滤规则,让指定的输出源执行行为操作。而每个行为操作函数,会返回一个“行为操作句柄组”对象,用于控制操作执行过程。

from melobot.adapter import ActionHandle, ActionHandleGroup, Echo

# melobot 支持多个输出源,因此返回多个句柄组成的句柄组
handle_group: ActionHandleGroup = await adapter.send(...)

# 如果像教程开始那样,只使用添加了一个输入输出源
# 那么实际只会产生一个句柄,使用下标可以获得句柄
handle = handle_group[0]
# 使用 len 返回句柄数量
num = len(handle_group)
# 也可使用迭代语法迭代各个句柄
for handle in handle_group:
    handle: ActionHandle
    ...

# 获取句柄包含的行为对象和对应的输出源
action = handle.action
out_src = handle.out_src

# 等待句柄,即是等待被输出源通知行为已完成
# 获取返回值,即是响应结果,这在 melobot 中一般称作回应对象
echo = await handle
# 但需要注意:melobot 的机制规定,输出源可以没有回应
# 因此 echo 可能为空
echo: Echo | None
# 等待整个组,将会获得列表
echoes: list[Echo | None] = await handle_group

# 当需要确保回应不为空时,无需判空,而可以用 unwrap 方法
# 例如取出组中第一个句柄的回应,为空时自动发出异常
echo: Echo = await handle_group.unwrap(0)
# 同理,也可以一次性取出所有回应,保证它们都不为空
echoes: list[Echo] = await handle_group.unwrap_all()

# 使用异步迭代接口,可以直接以迭代方式获取回应
# 但需要注意:迭代顺序按回应完成的先后顺序
async for echo, handle in handle_group:
    echo: Echo | None
    handle: ActionHandle
    ...
# 使用 unwrap_iter 接口,具有非空保证性
# 但需要注意:迭代顺序按回应完成的先后顺序
async for echo in handle_group.unwrap_iter():
    echo: Echo
    ...

因此,如果只是想要保证有序性(即等待操作完成),实际上非常简单:

# 保证所有输出源的操作完成后继续执行
await (await adapter.send(...))

对于 OneBot 的回应对象,可以使用 data 属性或 result 方法获取装有响应数据的字典。

# 访问需要的响应数据(data 字段与 OneBot 中的数据结构一致)
# 依然建议使用下标访问,因为会有精确的类型注解
if echo.is_ok():
    # OneBot 的 data 字段也可能为空
    # 使用 OneBot Echo 独有的 result 方法来确保非空
    data: dict | None = echo.data
    data: dict = echo.result()
    msg_id = data['message_id']

关于回应对象,更多请参考 API 文档中的内容:OneBot v11 回应

提示

不建议频繁等待行为操作。等待总是需要更多时间,大量使用会降低运行效率。

建议只在行为操作必须有序,或需要返回数据时才去等待。

句柄的本质是将操作和等待解耦。由此你可以发散自己的思维来使用它:例如安排一批操作,后续再集中等待,实现并发操作。

但只依靠行为句柄,是无法控制行为何时执行的,只能控制等待的时机。如果还需要控制执行时机,请使用 lazy_action() 上下文管理器展开惰性行为作用域

from melobot.adapter import lazy_action

with lazy_action():
    # 此作用域内,所有行为操作都不会自动执行
    # 只是先产生了一个 pending 状态的句柄
    hg = await send_text(...)
    # 当需要执行时,手动调用 execute 方法
    # 直接执行整个组内所有句柄
    hg.execute()
    # 执行组内某一句柄
    handle: ActionHandle = ...
    handle.execute()

特别注意:惰性行为作用域内,如果不执行 execute 就进行 await 会导致死锁。但死锁在执行 execute 后会被解除。

自定义行为

和自定义消息段类似,有时候我们总是会需要自定义的 OneBot 行为类型的。一般这样构造:

from melobot.protocols.onebot.v11 import Action

# 临时构造一个自定义行为
action = Action(type="action_type", params={"param1": 123456})

# 继承并构造一个新的 Action 类
class MyAction(Action):
    def __init__(self, param1: int) -> None:
        super().__init__("action_type", {"param1": 123456})

action = MyAction(123456)

# 通过 adapter 的通用 action 输出方法输出
await adapter.call_output(action)
handle_group = await adapter.call_output(action)

实际上,适配器所有行为操作,都是先在内部构建 Action 对象,再通过 call_output() 输出。

而所有 OneBot v11 的行为对象,也可以在文档 OneBot v11 行为类型 中找到。你完全可以手动构造,再使用 call_output() 输出,这适用于更精细的控制需求。

总结

本篇主要说明了行为操作函数的用法,及行为操作的流程控制。

下一篇将重点说明:事件预处理。