园长

学无止境 知行合一



QQ机器人保姆级平民搭建方法(新)
本文内容及部分所用图片来自上一篇教程,与文章内容可能有偏差但不影响部署
如果在阅读时发现了我的错误 烦请在评论区提出
本人技术不佳 还希望来访大佬指教

写在前面

nb2a16文章原址基于Nonebot2和go-cqhttp的机器人搭建 | 稚昂长 (yzyyz.top)

概述

本文将叙述如何在linux(deepin)下使用Nonebot2(下称nb)和go-cqhttp(下称gocq)创建QQ机器人,若你想使用nb和gocq创建机器人,你需要:

  1. Python环境(3.7.3以上)
  2. Python能力
  3. 读文档能力
  4. Linux能力(部署于linux)

windows下部署也是一样的,

准备

什么是nb?什么是gocq?
关于这部分内容,你可以从他们的官网找到:

nb文档: https://v2.nonebot.dev
gocq文档: https://docs.go-cqhttp.org/
你需要记住他们,因为你需要随时查阅文档

简单粗暴来说,gocq就是一个QQ,即你需要用它来登录,接收消息,发送消息;而nb则用来实现机器人的各种功能。
如下是他们的项目地址,记得去点个star哦~

nb:https://github.com/nonebot/nonebot2
gocq:https://github.com/Mrs4s/go-cqhttp

创作本文时,nb最新版本为2.0.0b1,gocq最新版本为`v1.0.0-beta8-fix2,请注意内容时效!


开始

安装nb

  • 安装脚手架
    通过脚手架安装(官方推荐):

    1
    pip install nb-cli

    安装脚手架时会自动安装nb

    安装时出现错误:

    • python版本不对
    • 缺少Microsoft Visual C++ 环境
    • …(请仔细看报错)
  • 安装适配器(使用脚手架安装时已经安装)

    1
    pip install nonebot-adapter-onebot

安装gocq

deepin

我们打开工作路径(这里我新建一个bot文件夹,bot下新建cq文件夹并打开

前往 Release下载最新版,注意与你的操作系统匹配

我这里下载 go-cqhttp_1.0.0-beta8-fix2_linux_amd64.deb, 并安装。

  • cq目录下执行go-cqhttp
  • 选择反向WebSocket回车
  • 此时目录下会生成配置文件config.yml
  • 对其进行配置:
    • QQ号及密码
    • 反向ws地址

使用任何适宜的编辑器对配置文件进行编辑,在行4、5填写要用来做机器人的QQ账号与密码。行96 内容进行修改:

universal: ws://your_websocket_universal.server

修改为

universal: ws://127.0.0.1:8080/onebot/v11/ws

8080是端口号,根据实际需求填写,修改完保存即可,再次执行./go-cqhttp,即可登录成功(有的账号需要扫码登录,根据提示即可)

如图代表登陆成功,下方黄色提示暂且放下不管。


其他Linux发行版

下载对应release后:

  • 使用tar -xzvf [文件名]解压
  • cd设置工作目录为解压后的文件夹
  • 再执行 ./go-cqhttp
  • 按照提示配置config.yml与上方配置相同

创建nb项目

返回bot,在此处打开终端并运行:

1
nb create

此时按照如下步骤创建项目

  • 输入项目名称 我这里输入了robot
  • 使用 选择src文件夹
  • 选择一款预置插件,我按下空格选择echo,回车
  • 按下空格键选择noebot v11回车
    (很多人这里不注意空格)

请使用任何适宜的软件(如记事本(不推荐))打开env.dev并做出修改,保证你在前面的步骤中对gocq配置的端口与此文件中的端口保持一致

PORT=8080

修改为

PORT=你使用的端口

那么现在在robot下打开终端,输入以下命令运行nonebot2:

1
nb run

此外,在pycharm中运行bot.py也是可行的

注意,需要让gocq和nb都运行,linux可使用进程守护一类的工具让他们后台运行,我个人是宝塔面板中的进程守护来解决这个问题

如图表示运行成功,你会发现gocq中的黄色提示变成了:

已连接到反向Web…..

也就是说,nb与gocq之间可以通信了

大功告成

现在,向你的机器人发送 /echo 123 看看会发生什么吧,不出意外地,你的机器人向你回复了123


nonebot2机器人的配置

.env.*的配置

.env.*文件为配置文件,可在任何插件中加载,官方示例

1
2
3
4
5
6
7
8
9
10
HOST=0.0.0.0  # 配置 NoneBot 监听的 IP/主机名
PORT=8080 # 配置 NoneBot 监听的端口
SUPERUSERS=["123456789", "987654321"] # 配置 NoneBot 超级用户
NICKNAME=["awesome", "bot"] # 配置机器人的昵称
COMMAND_START=["/", ""] # 配置命令起始字符
COMMAND_SEP=["."] # 配置命令分割字符

# Custom Configs
CUSTOM_CONFIG1="config in env file"
CUSTOM_CONFIG2= # 留空则从系统环境变量读取,如不存在则为空字符串

你可以将其复制到.env.dev

  • 其中COMMAND_START=["/", ""]是对命令起始字符进行配置,当你遇到 怎么让命令没有/也能被机器人识别这个问题时,请对它进行配置。这样,你的命令前就不同加/

  • 形如custom这样的配置,你可以自定义,只要确保为JSON格式即可,且大小写不敏感,
    你可以配置如:

    1
    BAN=["123456"]

    则在插件中可通过以下方式来调用(后续使用插件会讲)

    1
    2
    import nonebot
    myconfig = nonebot.get_driver().config.ban

其他配置

就我个人而言,使用.env.*是最常使用的,此外还可通过系统环境变量、bot.py来配置,参见官方:[基本配置](配置 | NoneBot “基本配置”)


nonebot2机器人的使用

相信你再前面的章节中,已经对 .env.dev做出了修改,这里不再赘述

bot.py中默认生成的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import nonebot
from nonebot.adapters.onebot.v11 import Adapter as ONEBOT_V11Adapter

# Custom your logger
#
# from nonebot.log import logger, default_format
# logger.add("error.log",
# rotation="00:00",
# diagnose=False,
# level="ERROR",
# format=default_format)

# You can pass some keyword args config to init function
nonebot.init()
app = nonebot.get_asgi()

driver = nonebot.get_driver()
driver.register_adapter(ONEBOT_V11Adapter)

nonebot.load_builtin_plugins("echo")

# Please DO NOT modify this file unless you know what you are doing!
# As an alternative, you should use command `nb` or modify `pyproject.toml` to load plugins
nonebot.load_from_toml("pyproject.toml")

# Modify some config / config depends on loaded configs
#
# config = driver.config
# do something...


if __name__ == "__main__":
nonebot.logger.warning("Always use `nb run` to start the bot instead of manually running!")
nonebot.run(app="__mp_main__:app")

阅读注释后(看不懂请使用翻译)我们大可去掉其中的注释,改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import nonebot
from nonebot.adapters.onebot.v11 import Adapter as ONEBOT_V11Adapter

nonebot.init()
app = nonebot.get_asgi()
driver = nonebot.get_driver()
driver.register_adapter(ONEBOT_V11Adapter)
nonebot.load_builtin_plugins("echo")
nonebot.load_from_toml("pyproject.toml")

if __name__ == "__main__":
nonebot.logger.warning("Always use `nb run` to start the bot instead of manually running!")
nonebot.run(app="__mp_main__:app")

这里的 nonebot.load_builtin_plugins("echo") 是加载此前选择的预置插件,我们暂时搁置,现在讲讲如何使用别人写好的插件

使用商店中的插件

(2022年1月24日16:24:05部分插件未适配最新版nb,请参考:📝 NoneBot2 商店插件兼容性报告 )

nb的商店中,有很多插件,比如这个黑丝插件

  • 安装方法一

    点击复制安装命令,你的剪贴板会复制这段命令:

    1
    nb plugin install nonebot_plugin_heisi

    这段命令你需要在 robot 文件夹下,打开终端执行,他会安装插件并将配置写进 pyproject.toml 中,这样你无需修改 bot.py 即可在nb运行的时候加载这个插件

  • 安装方法二
    若你在方法一中遇到了安装问题,一般情况下,你可以将上述命令视作

    1
    pip install nonebot_plugin_heisi

    在任何打开的终端中都可以执行这段命令,而值得注意的是:
    如果你使用了方法二,你需要在 bot.py中的 nonebot.load_from_toml("pyproject.toml")前添加一句:

    1
    nonebot.load_plugin("nonebot_plugin_heisi")

    此时你的bot.py应该是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    import nonebot
    from nonebot.adapters.onebot.v11 import Adapter as ONEBOT_V11Adapter

    nonebot.init()
    app = nonebot.get_asgi()
    driver = nonebot.get_driver()
    driver.register_adapter(ONEBOT_V11Adapter)
    nonebot.load_builtin_plugins("echo")
    nonebot.load_plugin("nonebot_plugin_heisi")
    nonebot.load_from_toml("pyproject.toml")

    if __name__ == "__main__":
    nonebot.logger.warning("Always use `nb run` to start the bot instead of manually running!")
    nonebot.run(app="__mp_main__:app")

    即加载 “nonebot_plugin_heisi” 这一插件

    注意:这是方法二才需要的,一般情况下你不用这样做

tips: 多数情况下,在插件右上角的图标中进入他的仓库页面,你可以找到插件的详细用法

完成安装后,重启nb,你会看到

表示 “nonebot_plugin_heisi” 插件已经成功导入,在插件的仓库页面我们可以看到它的用法:

故我们在.env.dev中做出配置,再重启nb,在群内发送.黑丝即可

编写插件

须知

我们回顾一下我们的目录结构

1
2
3
4
5
6
7
8
bot
├── cq
├── robot
│ └── ...
│ └── src
│ └── plugins
│ └── bot.py
│ └── ...

gocq在之前的操作中已经完成了他的使命,不再讨论。

robot下的src文件夹是你你再进行 nb create 的操作时自动生成的,若没有 src 文件夹,那必定会有一个 与项目名相同的文件夹,按照我的教程来说,在 robot下还有一个 robot文件夹。

继续拿 src 文件夹来说,在它下面有一个 plugins 文件夹,是插件目录,文件夹里的插件会在 nb启动时被加载。

这是在nb create 时配置好的,你可以在 pyproject.toml 看到:

1
2
3
[nonebot.plugins]
plugins = []
plugin_dirs = ["src/plugins"]

表示这个nb项目的插件目录是 src/plugins

接下来我将演示一个简单的插件编写,仅提供入门nb的一个思路,其他操作将写在后面,或另撰新文。

开始

官方推荐使用python包文件形式创建插件,即 :一个插件为一个文件夹,文件夹名即插件名,下含 __init__.py的文件以及其他文件,为一个合法的python包。我们创建一个名为 first_plugin 的插件,欲用它来调用一个每日早报的接口:

1
2
3
4
5
src
│ └── plugins
│ └── first_plugin
│ └── __init__.py
│ └── askjson.py

以pycharm为例,在创建插件时,可选择新建下的 python包,该操作会自动生成 __init__.py

编写

> askjson.py

我们先对askjson.py 进行编写(其实这种简单地操作没必要独立出来一个askjson文件,这里只是示例)

1
2
3
4
5
6
7
8
9
10
11
12
import httpx
from nonebot import logger


async def get_url() -> dict:
"""
:return: 早报图片链接
"""
url="https://api.iyk0.com/60s"
async with httpx.AsyncClient() as client:
r = (await client.get(url=url)).json()
return r

上述代码中使用了异步的httpx而不是requests:数据获取应尽量使用异步处理。我们访问 https://api.iyk0.com/60s这个接口,该接口会返回:

1
2
3
4
5
6
{
"code": 200,
"msg": "Success",
"imageUrl": "https://img01.sogoucdn.com/app/a/200692/621_2416_feedback_f1971058fc5c40eabc885fa7a18dba28.png",
"tips": "注意:新闻数据是在每天凌晨一点更新,如设定定时任务请在凌晨一点后调用本接口!谢谢配合!"
}

故我们在调用 get_url()时,会返回早报的图片链接(接口正常的情况下)

> __init__.py

在该文件中编写各类事件响应及处理逻辑。

我通常默认阅读本文的宁具有一定python基础,故某些点不做解释

通常,最常见的开始姿势如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import nonebot
from nonebot import on_command,logger
from nonebot.rule import to_me
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, MessageSegment
from . import askjson

sixty=on_command("60s",aliases={"早报","六十"},priority=2,block=True)
@sixty.handle()
async def _(bot:Bot,event:MessageEvent):
img_url=(await askjson.get_url())
if img_url:
await sixty.send(message=MessageSegment.image(img_url["imageUrl"]))
else:
logger.info('获取时出现错误')
  • 第2行导入的on_command 注册一个消息类型的命令处理器,也就是说,让机器人识别你的命令需要用到他。logger是nb 日志记录器对象。详见: NoneBot.log 模块
  • 第7行中的写法中:

    1. 60s表示命令名
    2. aliases 表示命令别名,即 60s早报六十 都会响应这个事件(60s哈哈哈早报xxx这类消息也会响应,若要只有60s三个字才响应,则可以使用on_regex正则,或者进入事件后再判断)
    3. priority表示优先级,数值越小越优先
  • 第8行,紧接着@sixty.handle() sixty被事件响应器的装饰器装饰从而成为事件响应器的事件处理函数,handle()是简单的为事件响应器添加一个事件处理函数,这个函数将会在上一个处理函数正常返回执行完毕后立即执行。

事件处理函数中,除bot外都是可选的

b1中,按需传入参数,bot也可以不传,反正这里可以这样写,读者日后慢慢会理解

  • 第10行调用我们编写的函数获取到链接,12行中send()函数根据event向触发事件的主体发送消息。点我查看参数

    我们在注册事件时传入了Event故这里可以直接sixty.send(message=xxx),我们也可以:bot.send(event=event,message=xxx)

    其中message参数是我们要发送的消息,可以是纯文本 message="你好" ,这里使用的 MessageSegment 是nb对cq协议的适配,可用于发送CQ码等(个人理解),即:

    1
    await sixty.send(message=MessageSegment.image(img_url))

    等价于(不推荐)

    1
    2
    3
    4
    5
    6
    7
    rely=[{
    "type": "image",
    "data": {
    "file": img_url
    }
    }]
    await sixty.send(message=rely)
    • 如果你要发送文本+图片,如下写法皆可:

      1
      await sixty.send(message="今日早报"+MessageSegment.image(img_url))
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      rely=[{
      "type": "text",
      "data": {
      "text": "今日早报"
      }
      },
      {
      "type": "image",
      "data": {
      "file": img_url
      }
      }]
      await sixty.send(message=rely)
  • 现在,运行nb,向它发送早报看会发生什么

    成了,此时你将在nb日志界面看到

    其中包含了:

    • 消息id -765866496,

    • 消息来源 1796031384

    • 消息内容 早报

    • 事件被那个插件的Matcher处理了

      在gocq的日志界面看到

      一般情况下,使用小号来登录gocq作为机器人,在发送群聊消息时,可能会出现:

      这种情况我们称之为 `喜报`

      若要解决这个问题,我的建议是:看脸。 你可以使用此账号挂在cq一段时间,挂的时间真得看脸,我的号就风控过一次,几分钟就好了;或者重启cq试试。 还有一种情况是:

      显而易见,你的账号被冻结了,我相信,屏幕前的您能收购鹅厂,您肯定不会让上述情况发生的,对吧(确信)

  • tips:发送本地图片:

    • 利用gocq+nb作为机器人发送本地图片时,若使用相对路径,要记得 路径是相对gocq的 而不是相对nb项目的,

      1
      await sixty.send(message=MessageSegment.image("file:///路径"))
      1
      2
      3
      4
      5
      6
      7
      rely=[{
      "type": "image",
      "data": {
      "file": "file:///路径"
      }
      }]
      await sixty.send(message=rely)

路径问题本人一般使用Path:

1
2
3
4
from pathlib import Path
img_path = Path() / "xxx.png"
# .resolve() 转为绝对路径
img_path2 = img_path.resolve()

上述img_path都是Path对象,需要使用时可str(img_path), 此为个人见解,若有错误,烦请在评论区指出

此外:python路径操作新标准:pathlib 模块 - 和牛 - 博客园 (cnblogs.com)

其他

官方配置文档 :配置 | NoneBot 。这里暂时用不到,有需要请阅读之。



 上一页

随笔

 评论




博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

本站使用 volantis 作为主题 。