消息操作

相关知识

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

消息操作作为 melobot 中最主要的行为操作,十分常用。

单条消息的构造

相关知识

如果你不知道单条消息的表示方式,有“cq 字符串”和“消息段”两种格式,建议先浏览:消息内容的数据结构

一般来说,发送纯文本内容是最普遍的,方法也十分简单:

from melobot.context import send

@plugin.on_xxx(...)
async def _():
    await send("你好啊")

如果要发送多媒体内容,首先要通过各自的消息段构造函数构造消息段对象,然后直接传入 send() 作为参数。

例如使用 image_msg() 构造图片内容:

from melobot.models import image_msg

@plugin.on_xxx(...)
async def _():
    # 构造一个“图片”消息段,然后发送
    img = image_msg("https://www.glowmem.com/static/avatar.jpg")
    await send(img)

其他消息段构造函数,及这些函数的参数,参考:消息段构造函数

单条消息中,自然可能有多种类型的消息段同时存在。此时这样处理:

from melobot.models import text_msg, image_msg

@plugin.on_xxx(...)
async def _():
    # 例如文本和图片同时存在:
    await send([
        text_msg("给你分享一张图片哦,这是 melobot 项目作者的头像"),
        image_msg("https://www.glowmem.com/static/avatar.jpg")
    ])

自定义消息段的构造

一般来说,melobot 自带的消息段构造函数已足够使用。但是某些 OneBot 实现程序,可能会支持自定义的消息段,这些自定义消息段,是 OneBot 标准中没有的

这时你可以使用 custom_type_msg() 来构造这些自定义消息段

例如在知名 OneBot 实现项目 OpenShamrock 中,存在一种自定义的消息段 touch 消息(戳一戳,双击头像)。对应的消息段数据结构如下:

{
    "type": "touch",
    "data": {
        "id": "1574260633"
    }
}

如何让 melobot 发送这种自定义的消息段?非常简单:

from melobot.models import custom_type_msg

@plugin.on_xxx(...)
async def _():
    touch = custom_type_msg("touch", {"id": "1574260633"})
    await send(touch)

# 或者再自行封装一下 :)
def touch_msg(uid: int):
    return custom_type_msg("touch", {"id": str(uid)})

@plugin.on_xxx(...)
async def _():
    await send(touch_msg(1574260633))

单条消息的其他发送方法

send() 可根据当前触发事件,自动定位向何处发送消息。如果想要自定义发送目标,也很容易。只需要将send() 换成 send_custom() 即可,它的第一参数与 send() 完全相同。

from melobot.context import send_custom

@plugin.on_xxx(...)
async def _():
    # 发送一个自定义目标的私聊消息,userId 为 qq 号
    await send_custom(..., isPrivate=True, userId=1574260633)
    # 发送一个自定义目标的群聊消息,groupId 为群号
    await send_custom(..., isPrivate=False, groupId=535705163)

如果要回复消息事件对应的那条消息,按照之前学到的,应该这样做:

from melobot.models import reply_msg, text_msg

@plugin.on_xxx(...)
async def _(e = msg_event()):
    # reply_msg 是消息段构造函数之一,用于构造回复消息段
    # 消息事件的 id 属性值存储消息的 id
    await send([reply_msg(e.id), text_msg("你好哇")])

这是十分繁琐的,但是“发送回复消息”这一行为也很普遍。使用 send_reply() 简化:

from melobot.context import send_reply

@plugin.on_xxx(...)
async def _():
    # send_reply 第一参数与 send 完全相同
    await send_reply("你好哇")

想要提前结束事件处理方法,一般会用 return

@plugin.on_xxx(...)
async def _(e = msg_event()):
    if e.sender.id != 1574260633:
        await send("你好~ 你不是我的主人哦")
        return
    # 接下来是机器人主人的处理逻辑
    await send("主人好")

finish() 可以把 return 简化掉:

from melobot.context import finish

# 刚才的代码,使用 finish 优化后
@plugin.on_xxx(...)
async def _(e = msg_event()):
    if e.sender.id != 1574260633:
        # finish 运行完就返回啦,不需要显式 return
        await finish("你好~ 你不是我的主人哦")
    await send("主人好")

在嵌套函数调用中,finish() 实际上可以退出任意深度的嵌套函数调用:

@plugin.on_xxx(...)
async def _():
    await a()
    await b()
    ...

async def a() -> None:
    ...
    # 退出 a 函数后,直接从 say_hi 的 a 函数调用点直接退出
    await finish(...)

async def b() -> None:
    ...
    # 退出 b 函数后,直接从 say_hi 的 b 函数调用点直接退出
    await finish(...)

同理,send_reply() 对应的提前结束版本是:reply_finish()。使用方法与 finish() 基本一致。但是它发送的是回复消息。

提示

finish()reply_finish(),只能在事件处理过程中使用。

使用 CQ 字符串

除使用消息段对象外,也可以使用CQ 字符串直接表示单条消息的所有消息内容。

只要是有 cq_str 参数的行为操作函数,设置 cq_str=True 后,此行为操作函数将不再认为字符串是纯文本内容。而认为字符串是可解释的 CQ 字符串。

@plugin.on_xxx(...)
async def _():
    # 第一参数是字符串,无论内容是什么,都是纯文本消息内容
    await send("[CQ:face,id=178]你好啊")
    # 启用了 cq_str 后,第一参数如果是字符串,将会被解释为 CQ 字符串
    # 如果存在有效的 CQ 字符串,将会被直接应用
    await send("[CQ:face,id=178]你好啊", cq_str=True)

提示

你可自行浏览 API 文档:行为操作函数,查看哪些支持 cq_str 参数。

警告

发送 CQ 字符串存在潜在的安全问题:

如果将用户输入(如 msg_text() 获得的字符串)作为 CQ 字符串的一部分发送出去,这将会造成“注入攻击”!用户可以构造包含恶意图片、语音的 CQ 码,让 bot 发送。

任何时候启用 cq_str 选项,如果拼接了用户输入,务必校验

转发消息的构造

相关知识

如果你不知道转发消息的表示,主要依托于转发消息段和消息结点,建议先浏览:转发消息与消息结点

转发消息段构造

构造“转发消息段”,使用 forward_msg() 函数:

from melobot.models import forward_msg

# forward_id 是转发 id,可通过 msg_event().get_datas("forward", "id") 获得
msg = forward_msg(forward_id)

此时,msg 变量已经是一条转发消息的等价表达了,直接使用 send 发送:

@plugin.on_xxx(...)
async def _():
    # 这里也可以使用其他能发送消息段的方法:send_reply, finish...
    await send(msg)

消息结点构造

构造“合并转发结点”,使用 refer_msg_node() 函数:

from melobot.models import refer_msg_node

# 这里的 msg_id 是已存在的消息的 id,可通过 msg_event().id 获得
refer_node = refer_msg_node(msg_id)

构造“合并转发自定义结点”,使用 custom_msg_node() 函数:

from melobot.models import custom_msg_node

# 第一参数是消息内容,与上述消息段发送方法的第一参数相同
# 后续参数是在转发消息中显示的,发送人昵称 和 发送人的qq号(int 类型)
node1 = custom_msg_node("你好", sendName="机器人", sendId=xxxxxx)

node2 = custom_msg_node(
    image_msg(...),
    sendName="机器人",
    sendId=xxxxxx
)

node3 = custom_msg_node(
    [text_msg(...), image_msg(...)],
    sendName="机器人",
    sendId=xxxxxx
)

将消息结点组成列表,就是一条转发消息的等价表达了,使用 send_forward() 来发送它:

from melobot.context import send_forward

@plugin.on_xxx(...)
async def _():
    await send_forward([refer_node, node1, node2, node3])

转发消息的其他发送方法

send_forward() 可根据当前触发事件,自动定位要向何处发送消息。同理,要自定义发送目标,将send_forward() 换成 send_custom_forward() 即可,它的第一参数与 send_forward() 完全相同。

from melobot.context import send_custom_forward

@plugin.on_xxx(...)
async def _():
    # 发送一个自定义目标的私聊转发消息,userId 为 qq 号
    await send_custom_forward(..., isPrivate=True, userId=1574260633)
    # 发送一个自定义目标的群聊转发消息,groupId 为群号
    await send_custom_forward(..., isPrivate=False, groupId=535705163)

总结

本篇主要说明了如何构造和发送各种消息。

下一篇将重点说明:其他行为操作及行为操作的等待与响应。