NoneBot QQ 机器人开发指南
目录:
QQ 开放平台 近期将把之前已经用于频道的 QQ 机器人 扩展到群聊和私聊场景。虽然功能限制较多,但由于其是官方 API,稳定性会比较好。本篇文章为使用 NoneBot 开发 QQ 机器人的教程,阅读本教程需要有一定 Python 开发基础。需要注意,本教程仅为相关文档的补充,请仔细阅读文中引用的文档。
前期准备
了解机器人能力
QQ 机器人应用于频道和群聊两个主要场景,可以在单聊、群聊、文字子频道、频道私信使用,提供的频道机器人能力可以参考 API 文档;群聊机器人能力暂未公开发布,可先参考 QQ Bot 开发者文档【内测版】。
需要注意的限制有:
- 发送消息文本中的链接需要经过 ICP 备案,并在 QQ 机器人管理端进行根目录文件验证绑定。其他含有格式为
英文.英文
的文本也无法发送,如bot.self_id
,可以考虑将.
替换为其他符号。 - 无法在群聊和文字子频道接收图片和视频。
- 无法在文字子频道和频道私信收发语音和文件,无法在群聊接收语音和文件。
- 所有群管理能力暂不对外开放。
创建沙箱频道
当前 QQ 机器人需要在沙箱频道中进行测试,请确保所使用的开发者 QQ 账号拥有 QQ 频道内测权限,并创建一个频道供机器人测试使用,并确保该频道人数小于 20 人。
私域机器人
公域 QQ 机器人只能响应 @机器人 后发送的消息,而频道私域机器人可以响应全量的文字子频道消息,并有额外的频道管理能力(群机器人目前只有公域)。如果想要创建不需要 @机器人 的频道私域机器人,请确保使用频道主的账号进行 QQ 开放平台的注册,并在创建机器人时选择类型为“私域”。
频道私域机器人额外能力:获取频道成员列表、删除指定频道成员、创建子频道、修改子频道信息、删除指定子频道、可以接收频道内发送的所有消息事件。
QQ 机器人介绍与开通
想要接入 QQ 机器人,首先需要在 QQ 开放平台进行注册,具体流程请查看 QQ 机器人文档的 接入流程 部分,可以使用企业或个人主体入驻(文档中第 2 步或第 3 步)。
完成开发者账号创建后,登录 QQ 开放平台 参考 接入流程 文档的第 4 步创建应用,在 “应用管理” → “机器人” 标签下点击 “创建机器人” 按钮。
成功创建机器人后在 开发设置 界面获取机器人的 BotAppID、机器人令牌、机器人密钥。
NoneBot 项目创建与配置
本文中的 NoneBot 均指 NoneBot2。NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的事件进行解析和处理,并以插件化的形式,按优先级分发给事件所对应的事件响应器,来完成具体的功能。
测试项目创建
如果未使用过 NoneBot,建议首先创建一个测试用的 Bot 工程以熟悉项目创建流程。
参考 NoneBot 文档的 快速上手 章节创建一个基于终端的交互式机器人实例,并测试是否正常工作。
项目创建与配置
上述测试成功后便可以创建开发 QQ 机器人使用的工程,命令行跳转到放置工程的目录,按照上述 快速上手 章节中 创建项目 部分的操作步骤,项目模板 选择 simple(插件开发者)
,并按下面的内容选择驱动器和适配器:
[?] 要使用哪些驱动器? HTTPX (HTTPX 驱动器), websockets (websockets 驱动器)
[?] 要使用哪些适配器? QQ (QQ 官方机器人)
完成项目创建后,参考 QQ 适配器文档 进行 QQ 适配器配置,打开项目目录下的 .env
文件(如果没有这个文件,请在创建时选择 simple
模板),添加以下内容:
QQ_IS_SANDBOX=true
QQ_BOTS='
[
{
"id": "xxx",
"token": "xxx",
"secret": "xxx",
"intent": {
"guild_messages": true,
"at_messages": false
}
}
]
'
将 id
token
secret
分别替换为在 QQ 开放平台获取到的 BotAppID、机器人令牌、机器人密钥。
以上配置为频道私域机器人,接收全量消息无需 @机器人,如果使用频道公域机器人,将 guild_messages
值改为 false
,at_messages
值改为 false
,或删去这两个配置项使用默认值。
完成以上配置后,运行机器人项目,在沙箱频道中 @机器人 并输入 /echo hello world
测试,如果收到回复则配置成功(此处需要 @机器人 是因为 echo
插件只接受与我相关的消息,文字子频道中需要 @机器人 触发)。
插件开发
插件编写基础
参考 NoneBot 文档中的 插件编写准备、事件响应器、事件处理、获取事件信息 四个部分进行示例插件的创建。
按照文档创建的插件是可以提供给任何适配器使用的,所以也适用于 QQ 适配器。这种方式的局限性在于无法适用适配器提供的特殊消息类型,而只能发送纯文本,要实现发送图片之类的功能,则需要根据所使用的适配器对发送的消息进行处理。
QQ 适配器消息处理
参考 NoneBot 文档中的 处理消息 部分,文档中是以 Console
适配器作为示例,与 QQ 适配器有部分不同,QQ 适配器中提供了以下消息段:
MessageSegment.text("abc")
: 文本消息段。MessageSegment.emoji("4")
: QQ 表情,ID 参考 QQ 机器人文档的 表情对象 部分。MessageSegment.mention_user("12345")
: 提及 @用户。MessageSegment.mention_channel("123")
: 提及 #子频道。MessageSegment.mention_everyone()
: 提及 @所有人。MessageSegment.image("http://example.com/image.png")
: 网络图片,需要后台绑定域名。MessageSegment.MessageSegment.file_image(image)
: 本地图片,可以传入bytes
/io.BytesIO
/pathlib.Path
。MessageSegment.ark(ark)
: Ark 消息,私域被动消息有 Ark 权限,参考 QQ 机器人文档的 发送 ARK 模板消息 部分。MessageSegment.embed(embed)
: Embed 消息,文档。MessageSegment.markdown(markdown)
: Markdown 模板消息和 Markdown 消息,需要内邀开通,文档。MessageSegment.keyboard(keyboard)
: Markdown 消息的按钮列表,需要内邀开通,文档。
使用以上消息段进行消息拼接,便可以使用 QQ 适配器的特有消息类型进行消息发送和回复。以 NoneBot 文档中的插件示例为例,可以修改为:
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.adapters import Message
from nonebot.params import CommandArg
# 文件系统路径 Python 标准库
# 用于创建本地文件路径
from pathlib import Path
# 从 QQ 适配器导入消息段
from nonebot.adapters.qq import MessageSegment
weather = on_command(
"天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True
)
@weather.handle()
async def handle_function(args: Message = CommandArg()):
# 提取参数纯文本作为地名,并判断是否有效
if location := args.extract_plain_text():
image = Path("data/image.png")
messaege = f"今天{location}的天气是..." + MessageSegment.file_image(image)
await weather.finish(messaege)
else:
messaege = MessageSegment.emoji("123") + "请输入地名"
await weather.finish(messaege)
QQ 适配器 API 调用
QQ 适配器提供了 API 的封装,可以直接使用 Bot 实例进行调用,QQ 适配器中提供的 API 可在 API 文档 中查看,推荐使用输入关键词加上自动补全(如使用 Visual Studio Code 的 Pylance 扩展)来快速找到对应的 API 封装名称和参数列表,示例如下:
from nonebot import on_command
from nonebot.adapters.qq import Bot, MessageCreateEvent
test = on_command("test")
@test.handle()
async def handle_function(bot: Bot, event: MessageCreateEvent):
guild = await bot.get_guild(guild_id=event.guild_id)
member = await bot.get_member(guild_id=guild.id, user_id=event.get_user_id())
await test.finish(member.json())
更加深入的开发请参考 NoneBot 文档。