【NoneBot2】第二章:基础插件编写指南第三节———会说话就多说点

  1. 1. 第二章:基础插件编写指南第三节———会说话就多说点
    1. 1.1. Message
      1. 1.1.1. CQ码 和 MessageSegment
      2. 1.1.2. 使用CQ码
      3. 1.1.3. 使用MessageSegment
    2. 1.2. call_api
      1. 1.2.1. 使用方法

第二章:基础插件编写指南第三节———会说话就多说点

本节中,将会学习 Message 的基本概念,以及 CQ码MessageSegmentcall_api 的基本用法。

Message

Message是 nonebot 中 OneBot v11 协议 Message 适配。简单来说,就是你能放进输入框的东西理论上都能用Message进行表示,例如普通的文字、图片或者表情等。我们今天要了解的 MessageSegment 本质上就是Message,和CQ码一样,是方便我们更方便构筑Message的一个工具。

Message不光可以对上述内容进行转义或表达,也可以对其进行拼接,例如

1
2
3
4
5
6
7
from nonebot.adapters.onebot.v11 import Message, MessageSegment
msg1 = Message('快来看涩图')
msg2 = MessageSegment.image('http://a.image.com/setu')
msg3 = Message('[CQ:at,qq=114514]')
msg = msg3 + msg1 + msg2

# msg的内容发到QQ里就是:@114514 快来看涩图【假装这是个涩图】

CQ码 和 MessageSegment

简单来说,CQ码MessageSegment 均是用于辅助开发者更方便的发送非文本类消息的构造方法,例如图片、语音和at等。

经历过酷Q时代的开发者应该还记得CQ码,在nonebot和gocqhttp中cq码依旧可以使用,而且cq收到的非文本类消息也大多会使用CQ码传递给nonebot,而mirai及其他非QQ客户端的协议是没有CQ码的概念的,并且gocqhttp的CQ码在版本更迭的时候CQ码的表达方式也可能会产生变化,因此目前官方和社区的主流意见是尽量避免直接使用CQ码而转而使用MessageSegment的。

但是,MessageSegment也并非万能。其一是在gocqhttp传回的数据中,大部分依旧是使用cq码构造的,这也就要求我们要响应这些数据的时候需要对cq码进行解析而无法使用MessageSegment。其二是在部分情况下使用MessageSegment发送部分内容的情况下gocqhttp会显示“消息为空”而无法发送,但使用cq码就可以发送的奇怪的bug,而且除我之外也有其他开发者遇到过这个问题,但由于此bug不可稳定复现,仍不清楚其成因以及是否解决。

总而言之,个人给出的建议是优先使用MessageSegment进行发送,在发送失败的情况下可以尝试cq码进行发送。(你问解析?解析不就只有CQ码可以解析吗ԅ(¯﹃¯ԅ))

使用CQ码

首先我们应该知道cq码的构造方法,一般情况下是 [CQ:type,key=value] 这种构造形式,具体的某个CQ码我们可以在gocqhttp的官方文档的 CQ code 板块进行查询。

同时,由于cq码中存在例如 []:= 等符号,因此需要对其进行转义才能够正常使用,方法也很简单,只需要用 Message() 即可转义,例如:

1
2
3
from nonebot.adapters.onebot.v11 import Message
raw_cqcode = '[CQ:at,qq=114514]'
cqcode = Message(raw_cqcode)

这样就可以是这个CQ码正常的被解析成一个@,而不是直接把CQ码原文发出去了。

使用MessageSegment

截止至2022.3.31日,在nonebot官方文档中并没有对onebot协议中的MessageSegment进行介绍,因此在这里将会结合源码和编辑器的提示进行讲解

MessageSegment的使用方法实际上非常简单,基本的用法就是 MessageSegment.xxx() ,并且正如上文所说,MessageSegment本质上是Message类,所以我们不需要用 Message() 对其进行转义。
首先我们先输入 MessageSegment. 然后查看编辑器的提示或转到源码,就能看到MessageSegment提供的方法了:

我们这里用image进行举例:

1
2
from nonebot.adapters.onebot.v11 import MessageSegment
setu001 = MessageSegment.image('file:///D:/learning_materials/setu/001.jpg')

这样setu001就是一个Message类的涩图了,理论上和 Message('[CQ:image,file=file:///D:/learning_materials/setu/001.jpg]') 是等效的。

MessageSegment由于功能很多,因此不在此一一演示了,想要用好MessageSegment,需要需要多加利用编辑器的自动补全和语法提示,这样就不必翻源码即可使用了。

call_api

call_api相对于前面介绍的功能更强大,且用法也不复杂,而且覆盖面更广。但之所以call_api并不是我们开发过程中的首选,因为它在一般情况下相对我们正常使用的方法来说,也是稍显复杂的,一般体现在需要填写更多的参数上和没有流程控制的能力。

目前call_api最主要的应用是用于补充nonebot无法原生支持的功能,例如获取用户昵称、同意好友请求或上传群文件这种比较不常见的功能。

个人理解其为类似“瑞士军刀”的存在,什么都能干但并不如专业工具,更何况某些情况下我们需要的功能也只能使用call_api进行实现呢(笑)。

使用方法

首先我们需要知道我们对接的客户端有什么样的api,这样我们才能对其发起请求。我们使用的gocqhttp的官方文档中对API有详细的介绍。

call_api的写法目前有两种,bot.call_api('xxx', **{key:value})bot.xxx(key=value)两种仅写法不同,实质并无影响,可根据自身喜好进行调整。

这里我们以“获取群信息”为例进行写法一的演示。

我们需要提供两个数据,其中no_cache为选填字段(是否为选填字段需要自行结合描述和实践进行判断),group_id为必填字段。

1
2
3
4
5
6
7
8
9
10
from nonebot.adapters.onebot.v11 import Bot
from nonebot import on_message

test = on_command('test')
@test.handle()
async def _(bot: Bot):
# call_api的写法一
data = await bot.call_api('get_group_info',**{
'group_id' : 123456
})

这样我们就获得了data这个json格式的返回值,然后进行简单的转义就可以读取了。
这里我们再演示一下和“发送消息”,并使用第二种方法进行演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from nonebot.adapters.onebot.v11 import Bot, Event
from nonebot import on_message
import ast

test = on_command('test')
@test.handle()
async def _(bot: Bot, event: Event):
# call_api的写法一
data = await bot.call_api('get_group_info',**{
'group_id' : 123456
})
# 对json进行转义
data = ast.literal_eval(str(data))
msg = f"群号 :{data['group_id']}\
\n群名称:{data['group_name']}\
\n成员数:{data['member_count']}"
# call_api的写法二
await bot.send(
event = event,
message = msg
)
# 不过,这里更推荐直接用响应器的send方法
# await test.send(msg)

由此可见,call_api是依赖于bot对象的,因此在一些特殊的响应器(例如aps)中,我们需要手动获取bot对象,这些就留到用到的时候再讲吧。