由于我本人已经从 NoneBot1.8.1 升级到了 NoneBot2,本系列文章之后的所提到的 NoneBot 均指代 NoneBot2。本文中 NoneBot 的配置仅适用于 NoneBot2 之前的版本,如需配置 NoneBot2 请阅读 官方文档,我有空再回来修改本文。

前言

诈尸回归·搬迁事宜·年终总结·展望未来这篇文章里我提起过,在今年年初时我曾在溯洄Dice3 的基础上改写自己的掷骰机器人。我把这个机器人称作 Dr.Sink ,即陈末的谐音(沉没)的英文。可惜直到八月时酷 Q 因为不可抗力停止服务时,我还是没有实现自己最开始立下的 TO DO List。这时我的面前有两个选择,更换 Dr.sink 到 Mirai 上运行或者停止运行。一开始我当然是选择要延续 Dr.Sink 的生命,可惜当时 MiraiNative 对于酷 Q 原生插件的兼容性还是有些问题(当然也可能是我的问题,毕竟 Java 白痴),诸多功能都出现了大大小小的 bug。由于我对 Java 不甚了解也无法 Debug,于是便放弃了 Dr.sink 的开发,结束了它短短八个月的服役。

愿我们在更开放的平行世界相遇。——酷 Q 官网

不过到了年底,有些事情迎来了转机。在酷 Q 全面崩盘之后,很多大佬投入到 Mirai 的开发中,原 CQHTTP 插件也被大佬们做出了兼容各种语言的 SDK,我这个除了 python 和 Cpp 什么也不会的废物就此找到了新的出路:使用 go-cqhttp 作为中间层,用 python 编写机器人的逻辑,也就是 nonebot 框架的基本原理。

之所以选择 NoneBot 作为机器人的框架,原因其实很简单,就是懒。有现成的框架不用,再去重复造轮子那岂不是____。(哦,原来我之前写 auto_video 的时候就重复造了很多轮子啊,那没事了)所以接下来就是讲述我学习和使用 nonebot 框架的经历了。

go-cqhttp

正如上文所说,在我下定决心放弃 Cpp 而使用 python 来开发机器人之后,我的面前就只剩下一个选择了:采用 cqhttp 作为兼容层(什么?你说 python-Mirai?很遗憾,python-Mirai 也基于 Mirai-Http-api 运行,绕了一大圈还是绕回了这一层,更蛋疼的是 Mirai-Http 和 cqhttp 还有一定的差别,更何况这个 python-Mirai 本身已经十个月没有更新了)。前面也提到了,我对于 Java 是一窍不通,MiraiConsole 简直就是我的噩梦,我更倾向于 Mirai-Go,于是最后我选择了 go-cqhttp 这个集成了 Mirai-Go 的符合 OneBot 标准的项目。

既然确定了要使用的项目,就开始行动吧。

从 release 界面下载最新版本的 go-cqhttp 并解压运行。

[WARNING]: 尝试加载配置文件 config.hjson 失败: 文件不存在
[INFO]: 默认配置文件已生成,请编辑 config.hjson 后重启程序.

第一次运行将会出现上面的提示,这时在文件夹里便会生成如下的 config.hjson 文件。

{
    uin: 0
    password: ""
    encrypt_password: false
    password_encrypted: ""
    enable_db: true
    access_token: ""
    relogin: {
        enabled: true
        relogin_delay: 3
        max_relogin_times: 0
    }
    _rate_limit: {
        enabled: false
        frequency: 1
        bucket_size: 1
    }
    ignore_invalid_cqcode: false
    force_fragmented: false
    heartbeat_interval: 0
    http_config: {
        enabled: true
        host: 0.0.0.0
        port: 5700
        timeout: 0
        post_urls: {}
    }
    ws_config: {
        enabled: true
        host: 0.0.0.0
        port: 6700
    }
    ws_reverse_servers: [
        {
            enabled: false
            reverse_url: ws://you_websocket_universal.server
            reverse_api_url: ws://you_websocket_api.server
            reverse_event_url: ws://you_websocket_event.server
            reverse_reconnect_interval: 3000
        }
    ]
    post_message_format: string
    use_sso_address: false
    debug: false
    log_level: ""
    web_ui: {
        enabled: true
        host: 127.0.0.1
        web_ui_port: 9999
        web_input: false
    }
}

此处我们需要注意的有 uinpassword ,即机器人的 QQ 账号、密码。

顺便一提,这个 encrypt_password 虽然能帮你加密密码,但是这样你每打开一次都要输入第一次设置的 KEY,只能给开发徒增烦恼。

再顺便一提,虽然 go-cqhttp 的文档里有个 Windows 懒人法,但实际上你之后还是要自己编辑 config.hjson ,所以我一点也不推荐。

然后我们便重启 go-cqhttp,如果登录成功的话应该会出现如下信息:

[2020-12-31 19:09:34] [INFO]: 当前版本:v0.9.36-fix3
[2020-12-31 19:09:34] [INFO]: 用户交流群: 721829413
[2020-12-31 19:09:34] [INFO]: 将使用 device.json 内的设备信息运行Bot.
[2020-12-31 19:09:34] [INFO]: 开始尝试登录并同步消息...
[2020-12-31 19:09:34] [INFO]: 使用协议: iPad
[2020-12-31 19:09:34] [INFO]: Protocol -> connect to server: 120.232.18.55:8080
[2020-12-31 19:09:34] [INFO]: Admin API 服务器已启动: 127.0.0.1:9999
[2020-12-31 19:09:35] [ERROR]: Protocol -> unknown group msg: 0
[2020-12-31 19:09:35] [INFO]: 登录成功 欢迎使用: Dr.Sink

如此一来,go-cqhttp 方面的准备工作就做完了,接下来就是 nonebot 的事情了。

nonebot

NoneBot 是一个基于 OneBot 标准(原 CQHTTP)的 Python 异步 QQ 机器人框架,它会对 QQ 机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。

除了起到解析消息的作用,NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。

NoneBot 在其底层与 OneBot 实现交互的部分使用 aiocqhttp 库,后者在 Quart 的基础上封装了与 OneBot 实现的网络交互。

得益于 Python 的 asyncio 机制,NoneBot 处理消息的吞吐量有了很大的保障,再配合 OneBot 标准的 WebSocket 通信方式(也是最建议的通信方式),NoneBot 的性能可以达到 HTTP 通信方式的两倍以上,相较于传统同步 I/O 的 HTTP 通信,更是有质的飞跃。

需要注意的是,NoneBot 仅支持 Python 3.7+。

首先我们得安装 nonebot 库。

pip install nonebot

当然我们也可以选择将源代码 clone 到本地再进行安装,此方法安装的一定是最新版本。

git clone https://github.com/nonebot/nonebot.git
cd nonebot
python setup.py install

接着新建一个 py 文件,例如我这里就新建 bot.py

import nonebot

if __name__ == '__main__':
    nonebot.init()
    nonebot.load_builtin_plugins()
    nonebot.run(host='127.0.0.1', port=8080)

这时如果你直接运行 bot.py ,你可能会得到这样的结果:

ujson module not found, using json
msgpack not installed, MsgPackSerializer unavailable
[2020-03-16 15:50:26,166 nonebot] INFO: Succeeded to import "nonebot.plugins.base"
[2020-03-16 15:50:26,166 nonebot] INFO: Running on 127.0.0.1:8080
Running on http://127.0.0.1:8080 (CTRL + C to quit)
[2020-03-16 15:50:26,177] Running on 127.0.0.1:8080 over http (CTRL + C to quit)
......
OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。

这是因为我们还没有在 go-cqhttp 里开启反向 ws。

现在我们打开之前的 config.hjson 文件,找到这几行。

ws_reverse_servers: [
    {
        enabled: false
        reverse_url: ws://you_websocket_universal.server
        reverse_api_url: ws://you_websocket_api.server
        reverse_event_url: ws://you_websocket_event.server
        reverse_reconnect_interval: 3000
    }
]

false 改为 true ,把下面三个参数也就是 reverse_urlreverse_api_url,reverse_event_url的值都改为 ws://127.0.0.1:8080/ws/ ,重新启动 go-cqhttp,我们便会在 bot.py 运行后的界面找到类似下面的一行信息:

[2020-12-31 19:29:12,835] 127.0.0.1:9005 GET /ws/ 1.1 101 - 7872

这时候打开 QQ,给你的机器人发送:

/echo 你好,世界

image-20201231193330853

这意味这我们成功运行了 NoneBot 的最小实例,打开了新世界的大门。