从零开始的QQ机器人P1
是扑通人不是普通人
2021年11月27日 20:40
收录于文集
共4篇

前言

这是一篇基于Mirai框架使用Python通过Http协议开发

本系列使用所有的源码皆开源到Github,开源协议GPLv3

在学习之前需要您知道并了解一些基本的数学知识

一定要动手实践!一定要动手实践!一定要动手实践!光看是看不懂的!

上一篇专栏确实对新人不太友好,本篇将采用从0开始,包括但不限于从开始安装配置讲起

已经配置好环境的同学可以跳过学前准备

一切开发旨在学习,请勿用于非法用途


学前准备

  1. 下载安装软件*Java SE 15.0.1:https://download.oracle.com/otn/java/jdk/15.0.1%2B9/51f4f36ad4ef43e39d0dfdbaf6549e32/jdk-15.0.1_windows-x64_bin.exe*Python 3.9.5:https://www.python.org/ftp/python/3.9.5/python-3.9.5-amd64.exe*Mirai-MCL开发框架:https://github.com/iTXTech/mirai-console-loader/archive/refs/heads/master.zip集成开发环境(PyCharm):https://www.jetbrains.com/zh-cn/pycharm/download/download-thanks.html?platform=windows&code=PCC带"*"的由于下载时间过长或有时无法访问,在这里已打成软件包(仅在以下地址下载)软件整合包:https://cowtransfer.com/s/e91a8c2aeda44e一定要下载完之后再往下看!!

  2. 安装Java开发环境*本次安装的是Java SE 15.0.1版本①打开“jdk-15.0.1_windows-x64_bin.exe”进行Java SE安装②一直点击“下一步”,等待进度条走完出现,关闭按钮③打开Cmd输入 “java -version"后查看是否出现 java version "15.0.1&#​34;字样④输入 “javac -version"后查看是否出现 javac 15.0.1字样且与上面版本号相同④安装PyThon开发环境

  3. 安装PyThon开发环境*本次安装的是PyThon 3.9.5版本①打开“python-3.9.5-amd64.exe”进行PyThon安装②选择在界面下面名为“Add Python 3.9 to PATH"前的复选框③点击上面第一个选项名为”Install Now④等待进度条走完后,点击Clos选项退出⑤打开Cmd输入”python“后查看是否出现Python 3.9.5字样④安装Mirai开发框架

  4. 安装Mirai开发框架*①将压缩包解压后,打开点击名为”mcl-1.2.2“文件夹②双击运行”mcl.cmd“文件③弹出黑色Cmd窗口后等待后续安装完成最下面出现”>_④关闭窗口将名为“mirai-api-http-v1.12.0.mirai.jar”文件移动到“mcl-1.2.2“文件夹中的“plugins”中⑤运行“mcl-1.2.2“文件夹中的"mcl.cmd"配置Mirai

  5. 配置Mirai*①打开”mcl-1.2.2“文件夹中的”config“文件夹,打开”Console”文件夹②右键“AutoLogin.yml”选择编辑③将文本中的“account: 123456"的“123456”改为想要作为机器人账号的数字账号④将文本中的“value: pwd"的“pwd”改为想要作为机器人账号的数字密码⑤其他的暂时不用动,保存并关闭“AutoLogin.yml⑥回到”config“文件夹,打开“net.mamoe.mirai-api-http”文件夹⑦右键“setting.yml”选择编辑⑧将“host: 0.0.0.0“中的”0.0.0.0”更改为“127.0.0.1⑨记住”authKey“的,之后的编写中要用到 ⑩其他保持默认保存后关闭即可

  6. 安装&配置集成开发环境*如果已经有适合开发Python程序的IDE可以跳过此步骤(本系列教程将使用PyCharm作为IDE)①下载IDE安装包,选择Communuty版本进行下载②双击打开安装程序,进行安装,一直”Next“完成安装更改成中文

打开之后的样子

点击New Project之后

创建完之后出现主界面

点击菜单栏的File

在Settings界面选择Plugins选项

在搜索栏中输入chinese

等待进度条安装完成,重启IDE

重启后就可以看到已经修改成中文了

到这里已经完成了全部的学前准备


IDE基本使用&python基本语法

已经会一点基本操作的同学可以跳过IDE基本使用

首先我们先写一个简单的PyThon程序

输出你好!

*知识点print函数*

没有格式化文件

这里就可以使用快捷键”Ctrl+Alt+L“进行格式化


创作第一个属于自己的机器

能看到这里已经超过了90%的人了!

先看最终效果

今天实现的效果

首先分析这个是怎么实现的?

分为四大步骤

初始化?为什么要初始化?我们要通过建立http协议建立通信,用来接收信息和返回信息

*Session*

首先使用Python来获取Session

这里我们将会用到Python中的request包

包?什么是包?

包是为了方便管理这些模块而产生的概念

那怎么调用包?

代码块
Python
自动换行
复制代码
import requests # 以调用request包为例
复制成功

那怎么用Python中的request包来获取Bot的Session呢?

代码块
Python
自动换行
复制代码
import requests # 调用名为request的包
req = request.post()#使用request包中的post函数并将返回值写去变量req里
复制成功

那怎么调用包中的函数呢?

调用的是request包中的post函数

那怎么用调用的函数实现HTTP请求呢?

代码块
Python
自动换行
复制代码
import requests # 调用名为request的包

url = "http://127.0.0.1:8080/verify"# 创建一个名为url的变量,其中的ip地址是在学前准备里规定好的,/verify地址从官方文档中读取到的

data = {"verifyKey": "U9HSaDXl39ksd918273hU"}# 创建一个名为data的变量,其中的json是在官方文档中找到的,键名为verifyKey的值是在mirai-api-http中生成的

req = requests.post(url,data=data)#使用request包中的post函数并将返回值写去变量req里

print(req.text) # 输出经过post请求返回的值其中.text是表示输出请求后得到的文本
复制成功

url组成

上面提到的ip地址和json的值都是在目录

mcl-1.2.2/config/net.mamoe.mirai-api-http/setting.yml

中设置好的

其中的host就是ip地址port就是冒号后面的数字authk就是键值

这样我们就完成了第一步获取Session的值

那如果在请求数据过程中出现错误怎么处理呢?

我们知道网页请求出现错误的时候会显示404网页请求成功时会产生一个200的状态码

所以我们判断请求是否成功就需要加一个判断条件

首先我们使用

代码块
Python
自动换行
复制代码
import requests # 调用名为request的包

url = "http://127.0.0.1:8080/verify"# 创建一个名为url的变量,其中的ip地址是在学前准备里规定好的,/verify地址从官方文档中读取到的

data = {"verifyKey": "U9HSaDXl39ksd918273hU"}# 创建一个名为data的变量,其中的json是在官方文档中找到的,键名为verifyKey的值是在mirai-api-http中生成的

req = requests.post(url,data=data)#使用request包中的post函数并将返回值写去变量req里

print(req.text) # 输出经过post请求返回的值其中.text是表示输出请求后得到的文本

print(req.status_code)# 输出经过post请求返回的值其中.status_code是表示输出请求的状态码
复制成功

可以看到在最后一行显示了状态码“200

在此基础上添加if判断句

if...esle...判断语句

由此就可以将上面的代码进行优化

代码块
Python
自动换行
复制代码
import requests  # 调用名为request的包

url = "http://127.0.0.1:8080/verify"  # 创建一个名为url的变量,其中的ip地址是在学前准备里规定好的,/verify地址从官方文档中读取到的

data = '{"verifyKey": "INITKEYcokUrLe4"}'  # 创建一个名为data的变量,其中的json是在官方文档中找到的,键名为verifyKey的值是在mirai-api-http中生成的

req = requests.post(url, data=data)  # 使用request包中的post函数并将返回值写去变量req里

if req.status_code == 200: # 如果状态码等于200则执行代码块1
  print(req.text)
else:
  print("请求错误")#返回错误信息
复制成功

运行程序,我们会得到这样一段json代码

代码块
Python
自动换行
复制代码
{"code":0,"session":"Bk9YUZW6"}
复制成功

虽然session键的值会和这里的值不同

那怎么去提取session键的值呢?

这就用到Python的两一个标准库“json”来解析json字符串

代码块
Python
自动换行
复制代码
import json # 同样是使用import关键字导入json包
复制成功

在json包里有一个名为"loads"的函数那他是干嘛用的?

json.loads 用于解码 JSON 数据。该函数返回 Python 字段的数据类型

现在我们就用json.loads函数解码JSON字符串

代码块
Python
自动换行
复制代码
import requests  # 调用名为request的包

import json # 同样是使用import关键字导入json包

url = "http://127.0.0.1:8080/verify"  # 创建一个名为url的变量,其中的ip地址是在学前准备里规定好的,/verify地址从官方文档中读取到的

data = '{"verifyKey": "INITKEYcokUrLe4"}'  # 创建一个名为data的变量,其中的json是在官方文档中找到的,键名为verifyKey的值是在mirai-api-http中生成的

req = requests.post(url, data=data)  # 使用request包中的post函数并将返回值写去变量req里

if req.status_code == 200: # 如果状态码等于200则执行代码块1
  req = json.loads(req.text)
  authkey = req['session']
  print(authkey)# 提取出键名为session的值
else:
  print("请求错误")#返回错误信息
复制成功

这样就得到了session的值,到这里认证会话就已经编写完成了

之后再进行Session绑定到Bot

和上面的方法相同

接着上面的代码继续编写

代码块
Python
自动换行
复制代码
import requests
import json

# 获取session部分
url = "http://127.0.0.1:8080/verify"
data = '{"verifyKey": "INITKEYcokUrLe4"}'
req = requests.post(url, data=data)
if req.status_code == 200:
    req = json.loads(req.text)
    authkey = req['session']
    print(authkey)
    # 绑定session部分
    url = "http://127.0.0.1:8080/bind"
    data = '{"sessionKey": "' + authkey + '","qq": 123456789}'  # 把QQ的值改为作为机器人的账号
    req = requests.post(url, data=data)
    if req.text == '{"code":0,"msg":"success"}':  # 如果绑定成功
        print(authkey)  # 打印出session
    else:
        print("绑定session失败!")
else:
    print("请求错误")  # 返回错误信息
复制成功

到这里session绑定也做完了

为了以后方便调用和维护方便,我们将它封装成函数

什么是函数?函数就像是加工厂,把材料&半成品建工程产品

函数的定义

那如何把上面的代码封装成一个函数呢?

代码块
Python
自动换行
复制代码
import requests
import json


def key():  # 定义为key的函数
    # 获取session部分
    url = "http://127.0.0.1:8080/verify"
    data = '{"verifyKey": "INITKEYcokUrLe4"}'
    req = requests.post(url, data=data)
    if req.status_code == 200:
        req = json.loads(req.text)
        authkey = req['session']
        print(authkey)
        # 绑定session部分
        url = "http://127.0.0.1:8080/bind"
        data = '{"sessionKey": "' + authkey + '","qq": 123456789}'  # 把QQ的值改为作为机器人的账号
        req = requests.post(url, data=data)
        if req.text == '{"code":0,"msg":"success"}':  # 如果绑定成功
            return authkey  # 使用return输出session的值
        else:
            print("绑定session失败!")
    else:
        print("请求错误")  # 返回错误信息
复制成功

封装成函数之后就可以轻松的调用并输出session了

代码块
Python
自动换行
复制代码
import requests
import json


def key():  # 定义为key的函数
    # 获取session部分
    url = "http://127.0.0.1:8080/verify"
    data = '{"verifyKey": "INITKEYcokUrLe4"}'
    req = requests.post(url, data=data)
    if req.status_code == 200:
        req = json.loads(req.text)
        authkey = req['session']
        # print(authkey)
        # 绑定session部分
        url = "http://127.0.0.1:8080/bind"
        data = '{"sessionKey": "' + authkey + '","qq": 123456789}'  # 把QQ的值改为作为机器人的账号
        req = requests.post(url, data=data)
        if req.text == '{"code":0,"msg":"success"}':  # 如果绑定成功
            return authkey  # 使用return输出session的值
        else:
            print("绑定session失败!")
    else:
        print("请求错误")  # 返回错误信息


if __name__ == '__main__':  # main可以当作任何程序的入口
    key = key() # 将函数key()输出的值写入变量局部key
    print(key) # 输出局部变量key的值
复制成功

这样初始化的步骤就全部完成了!好耶!


接收信息?需要什么步骤才能接收到?

Get json Massage!

首先获取信息

那怎么获取呢?

从文档里能找到名为“获取队列尾部”的查询地址

代码块
Python
自动换行
复制代码
import requests
import json


def key():  # 定义为key的函数
    #此处省略了key函数的内容实际操作的时候一定要加上!

def massage(keys):#定义一个新的函数,key为函数的输入值主要输入session的值
  url = 'http://127.0.0.1:8080/fetchMessage?sessionKey=' + keys + '&count=1'#在这里使用了函数的输入值key
  req = requests.get(url)# 使用requests包中的get函数进行获取信息字符串
  print(req)
  return req.text
  


if __name__ == '__main__':  # main可以当作任何程序的入口
    key = key() # 将函数key()输出的值写入变量局部key
    print(key) # 输出局部变量key的值
    massage = massage(key)
    print(massage)
复制成功

点击运行

运行完之后会出现以上内容

可以看到他返回了串json字符串

代码块
JavaScript
自动换行
复制代码
{"code":0,"msg":"","data":[]}
复制成功

由于没有发送任何信息所以msg和data里面都是空白的

先使用json模块解析这串字符串

代码块
Python
自动换行
复制代码
import requests
import json


def key():  # 定义为key的函数
    #此处省略了key函数的内容实际操作的时候一定要加上!

def massage(keys):  # 定义一个新的函数,key为函数的输入值主要输入session的值
    url = 'http://127.0.0.1:8080/fetchMessage?sessionKey=' + keys + '&count=1'  # 在这里使用了函数的输入值key
    req = requests.get(url)  # 使用requests包中的get函数进行获取信息字符串
    print(req)
    req = json.loads(req.text)  # 使用json库解析这段请求回来的json代码
    if len(req["data"]) != 0:  # 这里使用len函数来读取字符串的长度,当req["data"]的长度不等于0时执行以下操作
        return req #返回req的值


if __name__ == '__main__':  # main可以当作任何程序的入口
    key = key() # 将函数key()输出的值写入变量局部key
    print(key) # 输出局部变量key的值
    massage = massage(key) #将massage函数的值赋值到变量massage
    print(massage) #输出变量massage的值
复制成功

在这时我们要使程序循环运行用来获取新的信息

wile循环

和if有点类似 在条件那里如果为真(True)则循环执行代码块

代码块
Python
自动换行
复制代码
import requests
import json
import time # 引入time包


def key():  # 定义为key的函数
    #此处省略了key函数的内容实际操作的时候一定要加上!

def massage(keys):  # 定义一个新的函数,key为函数的输入值主要输入session的值
    url = 'http://127.0.0.1:8080/fetchMessage?sessionKey=' + keys + '&count=1'  # 在这里使用了函数的输入值key
    req = requests.get(url, headers={'Connection': 'close'}, stream=False)  # 使用requests包中的get函数进行获取信息字符串
    # print(req.text)
    req = json.loads(req.text)  # 使用json库解析这段请求回来的json代码
    if len(req["data"]) != 0:  # 这里使用len函数来读取字符串的长度,当req["data"]的长度不等于0时执行以下操作
        print(req)
        return req


if __name__ == '__main__':  # main可以当作任何程序的入口
    key = key()  # 将函数key()输出的值写入变量局部key
    # print(key)  # 输出局部变量key的值
    while True:  # 使程序进入一个死循环状态用来循环获取聊天信息
        massage = massage(key)# 调用massage函数进行获取
        time.sleep(1)# 引入time包实现程序的延迟操作,防止请求过快消耗过多资源
复制成功

运行程序并向Bot发送信息

发送信息后可以看到json文字串

来分析一下json字符串

代码块
JavaScript
自动换行
复制代码
{
  'code': 0, //code处理状态 0正常
  'msg': '',
  'data': [
    {
      'type': 'FriendMessage', //type是表示是什么类型的消息,FriendMseeage是好友普通消息
      'messageChain': [
        {
          'type': 'Source',
          'id': 15034,
          'time': 1637152325
        },
        {
          'type': 'Plain', //Plain代表这里是文字信息类型
          'text': 'hello' //text的值是发来的信息内容
        }
      ],
      'sender': { //sender是发送者信息
        'id': 3177045556, //id就是发送者的账号
        'nickname': '𐂂阿⃠良⃠良⃠木⃠馨⃠', //nickname就是发送者的名字
        'remark': '备注名' //remark是对你的备注名
      }
    }
  ]
}
复制成功

这一大串的信息怎么去提取需要的嘞?

还是使用json库来实现

根据发来的json字符串按照所需的键逐条分析:

  1. 在请求获取成功之后其中code键会返回一个状态码,当这个状态码为”0”时证明请求成功继续执行下面的步骤(需要if来判断请求码的值)

  2. data中第一个type中的值是代表消息类型消息类型的值为FriendMessage根据文档可知这是来自好友的消息类型

  3. datamessageChain储存的就是消息内容,在消息内容'type&#​39;: 'Plain&#​39;的组里储存的就是发来的信息内容

  4. datasender组中id的值时发送这的ID

以上4条就是需要我们提取的信息

那怎么使用Python中的json库进行解析和提取?

和上面提到的提取session的值类似,来继续改写massage函数

代码块
Python
自动换行
复制代码
def massage(keys):  # 定义一个新的函数,key为函数的输入值主要输入session的值
    url = 'http://127.0.0.1:8080/fetchMessage?sessionKey=' + keys + '&count=1'  # 在这里使用了函数的输入值key
    req = requests.get(url, headers={'Connection': 'close'}, stream=False)  # 使用requests包中的get函数进行获取信息字符串
    # print(req.text)
    req = json.loads(req.text)  # 使用json库解析这段请求回来的json代码
    if len(req["data"]) != 0:  # 这里使用len函数来读取字符串的长度,当req["data"]的长度不等于0时执行以下操作
    	if req['code'] == 0:  # 判断code值是否为0
       	data = {'type': req['data'][0]['type']}  # 定义一个新的json数组用来存储信息,同时将键“type”的值定义为在上面2中分析的消息类型
      print(req)
      return req
复制成功

到这里就完成了组条分析的1、2步

问么判断是否是好友信息呢?

在上面的步骤中我们把消息类型“type”的值提取出来了,加个判断句:当变量"data"等于“FriendMessage”时继续向下读取发送者信息(id)

代码块
Python
自动换行
复制代码
def massage(keys):
    url = 'http://127.0.0.1:8080/fetchMessage?sessionKey=' + keys + '&count=1'  # 在这里使用了函数的输入值key
    req = requests.get(url, headers={'Connection': 'close'}, stream=False)  # 使用requests包中的get函数进行获取信息字符串
    # print(req.text)
    req = json.loads(req.text)  # 使用json库解析这段请求回来的json代码
    if len(req["data"]) != 0:  # 这里使用len函数来读取字符串的长度,当req["data"]的长度不等于0时执行以下操作
        if req['code'] == 0:  # 判断code值是否为0
            data = {'type': req['data'][0]['type']}  # 定义一个新的json数组用来存储信息,同时将键“type”的值定义为在上面2中分析的消息类型
            if data['type'] == "FriendMessage":  # 过滤数据类型
                data['SenderQQ'] = str(req['data'][0]['sender']['id'])  # 提取发送者ID号写入字典中名为SenderQQ的键中
                data['SenderName'] = req['data'][0]['sender']['nickname']  # 提取发送者昵称写入字典中名为SenderName的键中
                print(data)
                return data
复制成功

拆分效果如下:

代码块
JavaScript
自动换行
复制代码
{'type': 'FriendMessage', 'SenderQQ': '3177045556', 'SenderName': '𐂂阿⃠良⃠良⃠木⃠馨⃠'}
复制成功

消息类型(type):FriendMessage,发送者账号(SenderQQ):3177045556,发送者昵称(SenderName):𐂂阿⃠良⃠良⃠木⃠馨⃠

经过以上的才分成功把发送者信息化简了,接下来解决信息内容怎么提取:

这里我们用到for...in...循环结构来实现将messageChain键中的数据进行拆分

历遍函数

for...in...可以将数组逐条读取,能逐条读取就可以逐条筛选信息

我们要逐条读取"messageChain"中的信息,所以我们要将&#​34;messageChain"放在”y“的位置

也就是这样:

代码块
JavaScript
自动换行
复制代码
for datalist in req['data'][0]['messageChain']:
复制成功

将"messageChain"中的信息,逐条读取出来加以判断

代码块
Python
自动换行
复制代码
def massage(keys):
    url = 'http://127.0.0.1:8080/fetchMessage?sessionKey=' + keys + '&count=1'  # 在这里使用了函数的输入值key
    req = requests.get(url, headers={'Connection': 'close'}, stream=False)  # 使用requests包中的get函数进行获取信息字符串
    # print(req.text)
    req = json.loads(req.text)  # 使用json库解析这段请求回来的json代码
    if len(req["data"]) != 0:  # 这里使用len函数来读取字符串的长度,当req["data"]的长度不等于0时执行以下操作
        if req['code'] == 0:  # 判断code值是否为0
            data = {'type': req['data'][0]['type']}  # 定义一个新的json数组用来存储信息,同时将键“type”的值定义为在上面2中分析的消息类型
            if data['type'] == "FriendMessage":  # 过滤数据类型
                data['SenderQQ'] = str(req['data'][0]['sender']['id'])  # 提取发送者ID号写入字典中名为SenderQQ的键中
                data['SenderName'] = req['data'][0]['sender']['nickname']  # 提取发送者昵称写入字典中名为SenderName的键中
                for datalist in req['data'][0]['messageChain']:
                  if datalist['type'] == "Plain":
                    data['Plain'] = str(dataList['text'])
                print(data)
                return data
复制成功

可以看到在Plain中就存有发来的信息了

代码块
Python
自动换行
复制代码
{'type': 'FriendMessage', 'SenderQQ': '3177045556', 'SenderName': '𐂂阿⃠良⃠良⃠木⃠馨⃠', 'Plain': '测试'}
复制成功

获取信息部分就完成了!

接下来就差处理&发送信息

我们先做出发送信息部分

代码块
Nginx
自动换行
复制代码
[POST] http://127.0.0.1:8080/sendFriendMessage
复制成功

调用以上路径进行信息的发送

具体发送方法和上面大同小异,所以这里直接上代码,具体看注释

代码块
Python
自动换行
复制代码
# key是session的值,Qid是要像谁发送的qq号,plain是要发送的内容
def send_message_person(key, Qid, plain):#型定义一个函数惊醒数据的发送
    url = "http://127.0.0.1:8080/sendFriendMessage"#上面提到的请求地址
    data = '{"sessionKey":"' + key + '","target":' + Qid + ',"messageChain":[{ "type":"Plain", "' + plain + '" },]}'# 请求信息
    requests.post(url,data=data)#使用post函数进行通信
复制成功

发送信息的部分就完成了

接下来是处理信息

上面的最终效果里不难发现机器人的回复去掉了"",&#​34;?"添加了&#​34;!"

那我们怎么去实现这个效果呢?

这就又要使用Python自带的方法strip("data&#​34;),它是用来替换开头或结尾的字符

利用这个方法使原信息去掉"",&#​34;?"

代码块
Python
自动换行
复制代码
str(massage["Plain"][0]).strip("吗??")
复制成功

大体就完成了:

代码块
Python
自动换行
复制代码
import time

import requests
import json


def key():
    #此处省略了key函数的内容实际操作的时候一定要加上!

def message(keys):  # 定义一个新的函数,key为函数的输入值主要输入session的值
    #此处省略了message函数的内容实际操作的时候一定要加上!


def send_message_person(key, Qid, plain):
    #此处省略了send_message_person函数的内容实际操作的时候一定要加上!


if __name__ == '__main__':  # main可以当作任何程序的入口
    key = key()  # 将函数key()输出的值写入变量局部key
    # print(key)  # 输出局部变量key的值
    while True:  # 使程序进入一个死循环状态用来循环获取json聊天信息
        message = message(key)
        if massage["Plain"][0] > 0: # 判断有没有信息
          send_massage = str(massage["Plain"][0]).strip("吗??")
          send_message_person(key, massage["SenderQQ"], send_massage)#调用函数进行发送
        time.sleep(1)
复制成功

源码放在这里 →  https://github.com/ExMikuPro/Mirai-Learn-Episode-one


参考资料

Mirai:https://github.com/mamoe/mirai

Mirai Console Loader(MCL):https://github.com/iTXTech/mirai-console-loader

Mirai-api-http:https://docs.mirai.mamoe.net/mirai-api-http/

gplv3:https://doc.yonyoucloud.com/doc/sfd-gpl/gplv3.html