文章目录
- 前言
- 捕获消息
- 处理消息
- Bot机器人参数
- Event事件参数
- 回复消息
- 字符串与Message
- 调用MessageSegment接口
前言
前面已经有不止一篇博客教大家如何搭建nonebot2环境了大家可以去专栏查看,这篇博客并不会再次带大家来搭建nonebot2环境,而是着手与插件的编写,也就是开始使用机器人处理QQ用户发来的消息。在说插件编写之前先带大家回顾一下Nonebot2机器人处理用户信息的整体过程。机器人后台接收消息时会经过如下流程:首先gocqhttp会从QQ服务器获取消息,然后通过接口将消息传输给Nonebot2,Nonebot2将消息处理的结果再发给gocqhttp端,最后gocqhttp根据发还的数据向QQ服务器发出消息,这就是消息收发与处理的整个流程。简单来说gocqhttp就是实现Nonebot2和QQ服务器之间通信的桥梁
,也就是眼睛耳朵鼻子或者说身体的各个部位,而Nonebot2就是相当于"大脑",负责处理接收到的信息并且发送指令给手、脚这种身体部位让他们行动,今天要编写的插件就是大脑中的神经元,不同的神经元负责处理不同类型的消息。
接下来将分为三部分进行介绍,捕获消息就是用户的消息触发到Nonebot2的神经元,处理消息就是Nonebot这个大脑如何处理捕获到的消息,回复消息是指做出了什么行为。先结合一个实例观察一下:
from nonebot import on_keyword
# nonebot2中的适配器这么引入
from nonebot.adapters.onebot.v11 import Message
helloword=on_keyword({"hello"})
@helloword.handle()
async def _():
await helloword.finish(Message("你好!"))
这段代码实现的功能是:如果有人发送(无论是与机器人的私聊还是群聊里)任一包含"hello"这个关键词的消息的时候,机器人会回复消息:“你好!”。
接下来的三部分将会由这段简短的代码进行展开叙述。在正式展开之前先对Nonebot2引入基本库进行一下说明。
from nonebot import on_keyword
from nonebot.adapters.onebot.v11 import Message
这段import代码的是从nonebot2库中引入on_keyword
方法,再从Onebot V11库中引入Message
方法,后面会介绍这两个有啥用。
Nonebot2中的很多功能都需要通过import导入后才能使用,这里列举了一些常见库里包含的常见函数:(大家只需先记住引入库从哪里引入即可)
库 | 常用函数/方法 | 说明 |
---|---|---|
nonebot | on_message,on_notice,on_request,on_keyword,on_command,on_regex | 基础库 |
nonebot.config | Config | 配置文件库 |
nonebot.matcher | Matcher | 事件响应器 |
nonebot.params | Arg,State,CommandArgs,RegexMatched | 参数库 |
nonebot.permission | SUPERUSER,Permission | 权限库 |
nonebot.log | logger,default_format | 日志库 |
nonebot.adapters.onebot.v11 | Bot,Message,MessageSegment,Event,PRIVATE,GROUP等 | Onebot库 |
注:OneBot V11的库可以从
nonebot.adapters.onebot.v11直接导入,也可以用诸如
nonebot.adapters.onebot.v11.message的方式导入
捕获消息
helloword=on_keyword({"hello"})
这行代码的含义是“创建一个名为helloword的变量,其为一个类型为on_keyword的事件响应器”,这段代码的作用就是当用户发送的信息包含hello时就触发其绑定的函数,而helloword的作用仅仅是方便找到这个响应器。在此大家可以了解一下有关on_keyword的参数。
on_keyword(keywords, rule=..., *, permission=..., handlers=..., temp=..., priority=..., block=..., state=...)
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应
-
第一个参数是
keywords
,类型为集合(Set),传入的是用来触发该响应器工作的关键词集合(用{}包起来的)。 -
第二个参数是
rule
,传入的是bool类型规则函数或者Rule类。 -
第三个参数是
permission
,传入的是bool类型的权限函数或者Permission类。 -
第四个参数是
handlers
,传入的参数是被事件响应器触发的函数列表。 -
第五个参数是
temp
,传入的是bool类型参数,若为True则此响应器只会使用一次。 -
第六个参数是
priority
,传入的是int型参数,用于设定响应器的优先级。注意!此参数越大优先级越低。 -
第七个参数是
block
,传入的是bool类型的参数,若为True则不会注册优先级小于此响应器的响应器,相当于消息被阻断在这里了。 -
最后一个参数是
state
,传入的是字典类型参数,用于存储响应器状态信息。
目前需要了解的只有keywords
,rule
,permission
,priority
和block
这四个参数,其他参数用法较为麻烦,我们之后才会讲到
-
首先是介绍的是
keywords
参数,这个参数作用比较简单,就是传入一个元素是字符串的集合,然后如果接收到的消息包含这个集合内的任一关键字,则触发该事件响应器。 -
第二个是
rule
参数,通过这个参数我们可以过滤掉想过滤掉的语句,有两种方式,一个是自定义规则,一个是使用官方的rule库。rule
参数的本质其实就是一个bool类型的变量或者返回值类型为bool的函数,当rule
值为True
时即可以触发事件响应器,反之为False
时则不会触发。或许有人会问在响应器处理函数当中判断是否符合条件和rule的功能是不是一样的?虽然作用上两者一样,但是ruler相当于从源头上"掐断"该事件响应器,而处理函数是从中途阻止。# 自定义规则函数(当@机器人时触发) from nonebot import on_keyword from nonebot.adapters.onebot.v11 import GroupMessageEvent def _checker(event: GroupMessageEvent) -> bool : return event.to_me helloword=on_keyword({"hello"},rule=_checker) # 使用官方的规则库(当@机器人时触发) from nonebot import on_keyword from nonebot.rule import to_me helloword=on_keyword({"hello"},rule=to_me())
-
第三个是
permission
参数,这个参数负责传入能触发此事件响应器的消息发送者类型,也就是哪些人能触发这个响应器。一般来说重要的指令都会加上一些权限限制,诸如操作机器人后台的一些指令。常见的permission有SUPERUSER(写在.env文件中的超级用户),GROUP_ADMIN(群管理员)和GROUP_OWNER(群主),这些是框架本身提供的。除此以外我们也可以自定义权限组,当然这个内容之后再谈。 -
第四个参数是
priority
,负责调控响应器触发的优先级,优先级越高,就越早被执行。举个例子,如有以下代码helloword=on_keyword({"hello"},priority=60) helloword_2=on_keyword({"hello"},priority=50)
两个响应器都以"hello"作为触发词,这时候如果接收到了包含"你是谁"的消息后,二者执行顺序就由
priority
值来决定了。注意!priority
值越小,优先级越高!如上面那个例子中helloword_2就会比helloword要更早触发。最后一个介绍的是
block
参数,这个参数一般情况下是和前面那个priority
参数搭配起来用,用以阻断消息传递。还是以上面那段代码为例helloword=on_keyword({"hello"},priority=60,block=True) helloword_2=on_keyword({"hello"},priority=50,block=True)
用户输入"hello"后并不会触发两个响应器,而是只会触发优先级更高的那个也就是helloword_2,触发完之后因为
block
值为True,所以消息传到这里就被阻断了,也就没办法触发优先级更低的响应器了。
处理消息
处理消息这部分需要先知道用户发送的什么然后才能处理,所以我们需要先了解Nonebot2的特性—依赖注入。
事件响应器函数常用的传入参数一般有以下三种:bot(机器人)
,event(事件)
以及args(参数)
,不过除此之外还有state,match等。这里我们先只讲前两个,第三个args参数我们暂时还用不到。
Bot机器人参数
首先是第一种参数:bot,bot类型的传入参数类就只有一个:Bot,下面是使用样例
from nonebot import on_keyword
from nonebot.adapters.onebot.v11 import Bot
helloword=on_keyword({"hello"})
@helloword.handle()
async def _(bot: Bot):
await helloword.finish(str(bot.self_id))
在上述代码中可以看到,Bot
作为一个参数传入事件响应器函数当中使用。Bot
是Nonebot库提供的一个类,当使用机器人的时候会自动创建一个Bot
类,然后我们可以通过这个类来调用与机器人交互的各种属性与方法。比如上面例子当中的self_id
就是Bot
类的一个属性,返回的数据是当前bot的QQ号,类型为int(所以才要转换为str类型,不然会报参数类型不匹配的错误,因为finish方法不支持传入int型参数)。
Bot类有如下的属性
属性名 | 返回值类型 | 说明 |
---|---|---|
self_id | int(整数型) | 机器人自己的QQ号 |
adapter | adapter(协议适配器) | 返回机器人所用的协议适配器 |
还有一些个人认为的Bot中比较常用的方法,篇幅有限其他方法以及具体的参数传递就不讲了,可以自行翻阅文档。
方法名 | 作用 |
---|---|
send_msg | 发送(群聊/私聊)消息 |
delete_msg | 撤回消息 |
set_group_ban | 设置群组禁言 |
set_friend_add_request | 设置好友验证是否通过 |
set_group_add_request | 设置加群验证是否通过 |
get_login_info | 获取登录信息 |
get_group_member_info | 获取指定群成员信息 |
get_group_honor_info | 获取群荣誉(龙王等)信息 |
Event事件参数
接下来是第二种参数:event事件参数,event类型其实包含很多类:如Event,GroupMessageEvent,PrivateMessageEvent等等。Event类一些方法/属性和Bot类重复,比如self_id
等,不过由于Event类下的方法/属性相较于Bot类更多,且通用性更好,所以要获取的信息相同时一般常用Event类来代替Bot类。下面列举了一些常见的Event类
类名 | 说明 |
---|---|
Event | 事件 |
GroupMessageEvent | 群消息事件 |
PrivateMessageEvent | 私聊消息事件 |
NoticeEvent | 通知事件 |
GroupUploadNoticeEvent | 群文件上传事件 |
GroupAdminNoticeEvent | 群管理员变动事件 |
GroupDecreaseNoticeEvent | 群人数减少事件 |
GroupIncreaseNoticeEvent | 群人数增加事件 |
GroupBanNoticeEvent | 群管理员禁言事件 |
FriendAddNoticeEvent | 好友增加事件 |
GroupRecallNoticeEvent | 群消息撤回事件 |
FriendRecallNoticeEvent | 私聊消息撤回事件 |
NotifyEvent | 提醒事件(这个文档没翻到) |
PokeNotifyEvent | 群戳一戳事件 |
HonorNotifyEvent | 群荣誉变更事件 |
LuckyKingNotifyEvent | 群红包运气王事件 |
RequestEvent | 请求事件 |
FriendRequestEvent | 好友申请事件 |
GroupRequestEvent | 入群申请事件 |
虽然没全部列举出来,但是这些类目前来说已经完全够用了,如何知道它们下面有哪些属性和方法呢?有两种方法:一是查阅官方文档,二是在支持代码补全的编辑器中输入对应的类名后再加上.
就可以跳出对应的提示,然后可以根据语法提示选择需要的方法/属性就行了。
最后再附上一个具体例子
from nonebot import on_keyword
from nonebot.adapters.onebot.v11 import Message,GroupMessageEvent
helloword=on_keyword({"hello"})
@helloword.handle()
async def _(event:GroupMessageEvent):
if(event.user_id==123456 and event.group_id==11111):
await word.finish(Message("OK!"))
此段代码作用:当群聊11111中里有一个QQ号为123456的人发送包含“hello”这个词的句子时,bot会发送“OK”。
回复消息
最后我们要解决的问题是:如何将处理好的消息发送出去,在此之前我们需要了解一下Onebot的消息类型。上面我们曾提到helloword.finish()
方法可以传入四种参数:
-
字符串(Str)
-
消息(Message)
-
格式化消息模板(MessageTemplate)
-
消息段(MessageSegment)
其中格式化消息模板用法比较特殊,暂且避开不谈,只讲剩下三种。
字符串与Message
字符串(Str)与消息(Message)大部分情况下可以等价,可以替代Message使用,但是有一种特殊情况:消息内包含CQ码的时候只能使用Message,不过个人建议还是统一使用Message来进行消息发送。
CQ码是go-cqhttp协议中的一种特殊的消息字段,用以发送QQ中的特殊消息(如图片、语音、@等等),常见的CQ码格式为[CQ:类型,参数=值,参数=值]
,如下面的例子就是CQ码的一种使用
举出一个CQ码回复消息的例子:
@word.handle()
async def _():
await word.finish(Message("[CQ:share,url=https://www.baidu.com,title=百度]"))
上述代码发送了一个百度的链接(不是以文字链接的方式),而是一个可以点击的类似“小程序”的执行框,点击后可以直接跳转。
(因为QQ接口的问题,此CQ码有时候会无法使用,会报错"消息可能被风控",请尝试其他的CQ码。)
这里我列举了一些常见的CQ码,全部的CQ码请查阅go-cqhttp文档或者OneBot文档。
类型 | 参数 | 作用 |
---|---|---|
face | id=表情ID | 发送QQ内的表情 |
record | file=文件路径/URL | 发送语音(一般为mp3格式) |
at | qq=QQ号,(name=昵称) | @某人(不在群里时可以使用name参数) |
share | url=网站链接,name=网站名 | 发送网站分享 |
music | type=歌曲平台,id=歌曲ID | 发送音乐 |
image | 参数较多,参见文档 | 发送图片 |
reply | id=回复的消息ID,text=消息内容 | 发送回复消息 |
调用MessageSegment接口
除了CQ码之外还有一种叫消息段(MessageSegment)
的东西,可以实现与CQ码相同的功能,并且还可以做一些CQ码无法做到的事情。此外官方推荐使用消息段来替代CQ码(原因:CQ码不方便+容易被注入),后续案例也都会使用消息段。用法如下
@word.handle()
async def _():
await word.finish(MessageSegment.share(url="https://www.baidu.com",title="百度"))
此段代码效果与CQ码回复无异。使用MessageSegment方式发送特殊消息更加方便明了
提示:MessageSegment和Message可以通过"+"直接拼在一起,形成"文字+图片"类型的消息,另外Message之间也可以相互拼接。Message相当于一个数组,可以容纳一定数量的同类(Message)以及MessageSegment
最后再回顾一下使用CQ码回复消息与使用消息段回复消息的区别:
CQ码
from nonebot import on_keyword
from nonebot.adapters.onebot.v11 import Message,GroupMessageEvent
from nonebot.rule import to_me
helloword=on_keyword({"hello"},rule=to_me())
@helloword.handle()
async def _(event: GroupMessageEvent):
await helloword.finish(Message(f"[CQ:at,qq={event.user_id}],你@我了!"))
消息段(推荐)
from nonebot import on_keyword
from nonebot.adapters.onebot.v11 import Message,GroupMessageEvent,MessageSegment
from nonebot.rule import to_me
helloword=on_keyword({"hello"},rule=to_me())
@helloword.handle()
async def _(event: GroupMessageEvent):
await helloword.finish(MessageSegment.at(event.user_id))