好久没水博客了,今天花点时间讲讲我最近玩的 QQ 机器人吧。之前是因为想在班级群里弄一个上课通知机器人所以就开始折腾了一下,但是昨天有小伙伴说要弄一个 “小玩具”,于是又开始了一波复习。这里总结一下搭建过程…

其实我之前就有接触过 QQ 机器人但是一直用别人的插件,词库啥的。也没那个本事自己写写代码来解决实际问题。而且前一段时间好像还听说因为腾讯的起诉酷Q的开发者删库了?然后很多小机器人软件纷纷关闭。不过这东西嘛,应该是打不死的,除非你腾讯提供官方渠道能让开发者们开发自己的 Bot,还需要比较大的自由度不然开发者们怎么会买账呢,封掉一个只会导致出现一个更强大的…在这里我向这些大佬表示崇拜。

好了回到主题,在一个贼帅气的小哥哥推荐下我看上了 mamoe/mirai (高效率 QQ 机器人框架 / High-performance bot framework for Tencent QQ)一个用 Kotlin 写的强大 QQ Bot 框架。GitHub Home:https://github.com/mamoe/mirai

不过,由于我想偷懒于是想用 PHP 直接扔服务器上执行。

于是我找到了 mirai-api-http 插件,Github Home:https://github.com/project-mirai/mirai-api-http

当然还少不了 mirai-console(mirai 的高效率 QQ 机器人控制台),Git Hub Home:https://github.com/mamoe/mirai-console

有了这 3 个东西,我就能愉快的用各种语言去实现自己想要的东西了除了 PHP 甚至是 Node.js 或者 Python 都能愉快的玩耍。由于我之前是用 php 来写的所以我这里的演示代码的话就全部用 php 了。

开始当然是环境了,必备的环境就是 java jdk 8 或者 jdk 11,其他根据自己的开发环境定比如我使用的是 php 那我肯定是需要 php 环境的,我的是 php 7.1,用 node.js 的话需要 node.js 环境,python 的话需要自己安装相应的 python 环境。

怎么安装 JDK 的话可以看看我的另外一篇博客,或者自己去找相关的博客看看:Ubuntu 19.10 手动下载安装 JDK8

开始

首先是部署 mirai 配置 mirai-console 和 mirai-api-http,当然我们先把编译好的 jar 包下载下来搭建项目目录。

mirai-api-http 的 jar 的话通过 GitHub 的 Releases 页面就可以直接下载到了,电梯直达:https://github.com/project-mirai/mirai-api-http/releases

mirai 和 mirai-console 的话可能就麻烦点,官方给出的搭建文档:https://github.com/mamoe/mirai-console/blob/master/docs/Run.md

如果你是 Linux 或者 MacOS 的话应该可以使用我整理好的集成包(自己手动搭建项目的话也可以参考一下):https://github.com/PBK-B/mirai-console-run

玩玩 mirai QQ 机器人,一起用 PHP 写一个 mirai QQ Bot-天真的小窝

我的项目结构如上:

  • config :mirai 插件配置文件夹
  • data :mirai 文件缓存文件夹
  • scripts :php 脚本程序或提供 api 的代码(主要开发目录)
  • libs :mirai 程序目录
  • logs :mirai 运行 log 文件夹
  • plugins :mirai 插件安装文件夹
  • run.sh :mirai 启动脚本

接下来我们需要了解一下 mirai-api-http 的工作流程:

玩玩 mirai QQ 机器人,一起用 PHP 写一个 mirai QQ Bot-天真的小窝

首先我们需要先生成获取一个临时的 session(根据官方描述是这个 session 在没有任何请求后的 30 分钟后自动销毁),这个 session 等于就是我们之后请求的密钥。然后需要讲 session 和一个 QQ 账号绑定在一起(好像只能绑定一个 QQ)。然后就能拿这个密钥请求任何 mirai-api-http 提供的接口了,就是这么简单。

接下来我们定义一些变量用来全局储存相关配置文件(其实可以用 .env 文件的)

$bot_qq 变量的话就是 QQ 账号,

$bot_authKey 是创建 Mirai-Http-Server 时生成的 key ,可在启动时指定或随机生成,在 config/MiraiApiHttp/setting.yml 文件的 authKey 字段可以看到

$bot_sessionKey 用来缓存获取到的 session

$bot_server_ip 就是服务器的 ip 和端口了,$bot_api 是拼接了一下 api

$bot_qq = "123456";
$bot_authKey = "xxxxxxxxx";
$bot_sessionKey;
$bot_server_ip = "0.0.0.0:8081";
$bot_api = "http://" . $bot_server_ip;

首先是生成获取一个 session API 参考

[POST] /auth

{
    "authKey": "U9HSaDXl39ksd918273hU"
}

// 先拿到接口的 sessionKey
$sessionKey_api = $bot_api . "/auth";
$sessionKey_body = '{ "authKey": "' . $bot_authKey . '" }';
$sessionKey_str = get_url_phone($sessionKey_api, $sessionKey_body);
$sessionKey_json = json_decode($sessionKey_str, true);
$bot_sessionKey = $sessionKey_json['session'];
if (!$bot_sessionKey) {
    echo "bot sessionKey 获取失败!请检查机器人服务是否启动!";
    exit;
}

(注意:其中的 get_url_phone() 函数是一个朋友分享给我的网络请求工具函数,我放在本博客的最后了,有需要的小伙伴自取,可以直接用自己习惯的各种 POST 请求方法代替)

然后用获取到的 session 绑定已经登陆的 QQ 号,API参考

[POST] /verify

{
    "sessionKey": "UnVerifiedSession",
    "qq": 123456789
}

// 绑定机器人
$verify_api = $bot_api . "/verify";
$verify_body = '{ "sessionKey": "' . $bot_sessionKey . '", "qq": ' . $bot_qq . ' }';
$verify_str = get_url_phone($verify_api, $verify_body);
$verify_json = json_decode($verify_str, true);
if (!$verify_json['code'] == 0) {
    echo onError(-1, "bot 绑定失败!" . $verify_json['msg']);
    exit;
}

处理完了就可以使用相关 api 处理一些事情了,如果需要处理时间监听的话还需要开启 websocket 然后让自己的程序与 mirai-api-http 建立 socket 连接,我这里就简单展示几个 api 请求。

发送好友消息 API参考

[POST] /sendFriendMessage

{
    "sessionKey": "YourSession",
    "target": 88363033,
    "messageChain": [
        { "type": "Plain", "text": "hello\n" },
        { "type": "Plain", "text": "world" },
	{ "type": "Image", "url": "http://cos.haxibiao.com/images/5fd199b7df23f.png" }
    ]
}

// 发送普通的消息!
$send_api = $bot_api . "/sendFriendMessage";
$send_body = '{ "sessionKey": "' . $bot_sessionKey . '", "target": ' . $bot_groupId . ', "messageChain": [ { "type": "Plain", "text": "' . $bot_msg_str . '" } ] }';
$send_str = get_url_phone($send_api, $send_body);
$send_json = json_decode($send_str, true);
if (!$send_json['msg'] || $send_json['msg'] != "success") {
    echo "消息发送失败!" . $send_str;
    exit;
}
echo "消息发送成功!";

其他更多的玩法推荐查看 mirai-api-http 文档:https://github.com/project-mirai/mirai-api-http/blob/master/docs/API.md

MiraiApiHttp/setting.yml 文件模版(来源:mirai-api-http GitHub 文档)

## 该配置为全局配置,对所有Session有效

# 可选,默认值为0.0.0.0
host: '0.0.0.0'

# 可选,默认值为8080
port: 8080          

# 可选,默认由插件第一次启动时随机生成,建议手动指定
authKey: 1234567890  

# 可选,缓存大小,默认4096.缓存过小会导致引用回复与撤回消息失败
cacheSize: 4096

# 可选,是否开启websocket,默认关闭,建议通过Session范围的配置设置
enableWebsocket: false

# 可选,配置CORS跨域,默认为*,即允许所有域名
cors: 
  - '*'

## 消息上报
report:
# 功能总开关
  enable: false
  # 群消息上报
  groupMessage:
    report: false
  # 好友消息上报
  friendMessage:
    report: false
  # 临时消息上报
  tempMessage:
    report: false
  # 事件上报
  eventMessage:
    report: false
  # 上报URL
  destinations: []
  # 上报时的额外Header
  extraHeaders: {}

## 心跳
heartbeat:
  # 功能总开关
  enable: false
  # 启动延迟
  delay: 1000
  # 心跳间隔
  period: 15000
  # 心跳上报URL
  destinations: []
  # 上报时的额外信息
  extraBody: {}
  # 上报时的额外头
  extraHeaders: {}

其他

分享一个朋友给我的 php 请求工具函数(大佬们可以直接忽略,我这里只是刚好把这个代码存到这里以备之后使用)

function get_url_phone($url, $post = 0, $referer = 0, $cookie = 0, $header = 0, $ua = 0, $nobaody = 0, $ifjosn = 0)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $httpheader[] = 'Accept: */*';
    $httpheader[] = 'Accept-Encoding: gzip,deflate,sdch';
    $httpheader[] = 'Accept-Language: zh-CN,zh;q=0.8';
    $httpheader[] = 'Connection: close';
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    if ($post) {
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
        if ($ifjosn == 1) {
            $httpheader[] = 'Content-Type: application/json; charset=UTF-8';
        } else {
            $httpheader[] = 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8';
        }
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
    if ($header) {
        curl_setopt($ch, CURLOPT_HEADER, true);
    }
    if ($cookie) {
        curl_setopt($ch, CURLOPT_COOKIE, $cookie);
    }
    if ($referer) {
        if ($referer == 1) {
            curl_setopt($ch, CURLOPT_REFERER, 'http://m.qzone.com/infocenter?g_f=');
        } else {
            curl_setopt($ch, CURLOPT_REFERER, $referer);
        }
    }
    if ($ua) {
        curl_setopt($ch, CURLOPT_USERAGENT, $ua);
    } else {
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Linux; Android 7.0; MI 10s Plus Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36');
    }
    if ($nobaody) {
        curl_setopt($ch, CURLOPT_NOBODY, 1);
    }
    curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $ret = curl_exec($ch);
    curl_close($ch);

    return $ret;
}

今天的博客就到这里吧,好久不见老伙计。