博主头像
知鱼杂记

春风若有怜花意 可否许我再少年

利用PHP搭建QQ官机/机器人

QQ官方机器人简介
1.消息通知给开发者使用websocket服务
2.发送消息,使用openapi即http
3.官方只给了go和node.js的SDK,没有PHP的
4.官方文档:https://bot.q.qq.com/wiki/develop/api/
5.再主动发送消息数量有限制,不同机器人数量不同
6.公域机器人,即其他人搜索即可添加到自己的频道或群,主动发送消息每日数量很少,好像每日就几条
7.私域,即需要开发者设置允许哪个频道或群添加,才能使用。主动消息数量较多,好像调整到了每日100。
8.被动回复消息都不限量

在gitee找到的一个的例子

介绍
使用phrity/websocket库搭建websocket服务

安装需求
1.PHP > 8.0
2.composer
3.phrity/websocket 版本大于等于2.0

安装步骤
1.机器人目录(自定义)下新建composer.json文件,输入


{
    "require": {
        "phrity/websocket": "^2.0"
    }
}

2.打开ssh,进入机器人目录,输入命令composer install 回车执行
3.在机器人目录新建GuildSocket.php文件

<?php

use WebSocket\Client;
use WebSocket\Connection;
use WebSocket\Message\Message;
use WebSocket\Middleware\PingResponder;

class GuildSocket
{
    private $qqGuildUrl = '';
    private $appId = '';
    private $token = '';
    private $appSecret = '';
    private $access_token = '';
    private $expires_in = '';
    private $guzzleOptions = [];
    private $s = '';
    private $session_id = '';
    private $time0 = 0;
    private $seconds = 0;
    
    /**
     * 设置最大执行时间设置为无限制
     * 设置内存限制设置为无限制
     * 初始化参数
     * 
     */
    public function __construct(String $qqGuildUrl, String $appId, String $token, String $appSecret, Array $guzzleOptions)
    {
        
        set_time_limit(0);
        ini_set('memory_limit','-1');
        $this->qqGuildUrl = $qqGuildUrl;
        $this->appId = $appId;
        $this->token = $token;
        $this->appSecret = $appSecret;
        $this->guzzleOptions = $guzzleOptions;
        
    }

    /**
     * @param $token
     * @return mixed
     * 获取Gateway
     */
    private function getGateway(String $token): string
    {
        return "wss://sandbox.api.sgroup.qq.com/websocket";
    }
    
    /**
     * @param $url  请求地址
     * @param  $method  请求方法
     * @param  $param  请求参数
     * @param  $headers  请求头
     * 构造HTTP请求
     * 
     */
    private function httpRequest($url, $method = "POST", $param = "", $header = [])
    {
    
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
        $response = curl_exec($ch);
        curl_close($ch);
        $response = json_decode($response, true);
        
        return $response;
        
    }
    
    
    /**
     * 获取openapi的调用凭证access_token
     */
    private function getSendMsgToken()
    {
        
        $param = [
            "appId"=> $this->appId,
            "clientSecret"=> $this->appSecret
            ];
        $token = $this->httpRequest("https://bots.qq.com/app/getAppAccessToken", "POST", json_encode($param), ['Content-Type: application/json']);
        
        if(!empty($token['access_token'])) {
            
            // 更新token及token失效时间
            $this->access_token = $token['access_token'];
            $this->expires_in = $token['expires_in'] + time();
            echo("\033[32m[消息发送token更新]\033[0m\n" . $token['access_token'] . "\n\n");
            
        }
        
    }

    /**
     * @param String $token
     * @param Client $client
     * @return String
     * WS身份验证,返回SessionID
     */
    private function idVerify(String $token, Client $client): string
    {
        $data = [
            'op' => 2,
            'd' => [
                'token' => "Bot " . $this->appId . "." . $this->token,
                'intents' => 2081166851,
                'properties' => []
            ]
        ];
        $client->text(json_encode($data));
        
        $receive = $client->receive()->getContent();
        
        $session_id = json_decode($receive, true)['d']['session_id'];
        
        echo("\033[32m[身份鉴权完毕]\033[0m\n session_id:" . $session_id . "\n\n");
        
        return $session_id;
    }

    /**
     * 建立WS连接
     */
    public function connect()
    {

        //获取WS连接路径
        $gateway = $this->getGateway($this->token);

        //创建连接
        $this->s = '';
        
        $client = new Client($gateway);

        // //获取心跳间隔
        $this->seconds = intval($this->getHeartBeat($client));
        echo("\033[32m[连接成功]\033[0m\n心跳间隔:" . $this->seconds . "\n\n");
        
        //身份鉴权
        $this->session_id = $this->idVerify($this->token, $client);
        
        // 获取发送消息鉴权token
        $this->getSendMsgToken();
        
        //首次心跳
        $this->time0 = time();
        $client->text(json_encode(['op'=>1, 'd'=>null]));

        // //消息监听
        $client->setTimeout($this->seconds)
            // Add standard middlewares
            ->addMiddleware(new PingResponder())
            // Listen to incoming Text messages
            ->onText(function (Client $client, Connection $connection, Message $message) {
                
                //将消息转换为数组
                $receiveArr =json_decode($message->getContent(), true);

                //如果op存在
                if (isset($receiveArr['op'])){

                    //排除心跳pong
                    //if($receiveArr['op']!=11){}

                    //如果是服务端推送,将消息派发到队列处理
                    if($receiveArr['op']==0){
                            
                        // 写入最新消息识别码s
                        $this->s = $receiveArr['s'];
                        echo("\033[34m[收到消息]\033[0m\n" . $receiveArr['d']['content'] . "\n\n");
                        
                        // 传递消息给消息处理类
                        GuildMessage::msgDispatch($this->qqGuildUrl, $this->appId, $this->access_token, $receiveArr);
                        
                    }

                    //如果服务端通知重连
                    if($receiveArr['op'] == 7){
                        
                        $client->text(json_encode(['op'=>6, 'd'=>['token'=>"Bot ".$this->appId.".".$this->token, 'session_id'=>$this->session_id, 's'=>$this->s]]));
                        
                    }

                }


            })
            ->onTick(function (Client $client){
                
                //检测是否到心跳时间
                $time1 = time();
                if($time1 - $this->time0 > $this->seconds - 20){
                    $client->text(json_encode(['op'=>1, 'd'=>$this->s]));
                    echo("\033[32m[心跳成功]\033[0m\n消息识别码(s):" . $this->s . "\n\n");
                    $this->time0 = $time1;
                };
                
                // 更新openapi调用鉴token
                if($this->expires_in - $time1 < 60) {
                    $this->getSendMsgToken();
                }

            })
            ->onError(function (Client $client){
                //重新连接
                $client->text(json_encode(['op'=>6, 'd'=>['token'=>"Bot ".$this->appId.".".$this->token, 'session_id'=>$this->session_id, 's'=>$this->s]]));
            })
            ->start();

    }

    /**
     * @param $client
     * @return float
     * 获得心跳时间
     */
    public function getHeartBeat($client)
    {
        
        $receive = $client->receive()->getContent();
        $initReceive = json_decode($receive, true);
        return floor($initReceive['d']['heartbeat_interval']/1000);
        
    }
  
  
}

4.新建qBot.php文件

<?php

require './vendor/autoload.php';

// websocket服务管理类
require "GuildSocket.php";
// 消息处理类
require "GuildMessage.php";
// phrity/websocket库,要求2.0以上版本
use WebSocket\Client;

$qqGuildUrl='https://sandbox.api.sgroup.qq.com';  // 沙盒环境接口
// $qqGuildUrl='https://api.sgroup.qq.com';  // 正式环境接口

$appId = "";  // QQ机器人ID
$token = '';  // 机器人toekn
$appSecret = "";  // 机器人密钥
$guzzleOptions = ['verify' => false];

$guild = new GuildSocket($qqGuildUrl, $appId, $token, $appSecret, $guzzleOptions);

$guild->connect();

5.创建文件GuildMessage.php,这个文件是消息处理文件

<?php

/**
 * 消息处理类
 */
class GuildMessage
{
    
    /**
     * 接收消息
     * 
     */
    public static function msgDispatch(String $qqGuildUrl, String $appId, String $access_token, Array $receiveArr) {
        
        // 事件类别
        $eventType = $receiveArr['t'];
        // 消息内容
        $receiveMsgArr = $receiveArr['d'];
        // 构建发送子频道消息接口
        $postUrl = $qqGuildUrl . "/channels/" . $receiveMsgArr['channel_id'] . "/messages";
        // 构建回复消息
        $sendMsgArr = [
            "msg_id"=> $receiveArr['id'],
            ];
        
        $content = '';
        
        // @机器人的消息处理
        if($eventType == "AT_MESSAGE_CREATE") {
            
            $content =  self::msgAtBot($receiveMsgArr);
            
        }
        
        if(!empty($content)) {
            
            $sendMsgArr['content'] = $content;
            $headers = [
                  'Authorization: QQBot ' . $access_token,
                  'X-Union-Appid: ' . $appId,
                ];

            // 发送消息
            self::httpRequest($postUrl, "POST", json_encode($sendMsgArr), $headers);
            
            echo("\033[34m[发送消息]\033[0m\n".$content."]\n\n");
            
        }
        
    }
    
    
    /**
     * @机器人消息处理事件
     * return 返回消息内容(文本消息)
     * 
     */
    private static function msgAtBot(Array $receiveMsgArr) {
        
        // 消息内容
        $msgContent = preg_match('/<@!.*?>\s*(.*)/', $receiveMsgArr['content'], $matches);
        $msgContent = $matches[1];
        
        $content = self::httpRequest("https://api.lolimi.cn/API/AI/wx.php?msg=" . $msgContent, "GET")['data']['output'];
        
        return $content;
        
    }
    
    
    /**
     * 构建http请求
     * 
     */
    private static function httpRequest($url, $method = "POST", $param = "", $headers = array()) {
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($headers, ['Content-Type: application/json']));
        curl_setopt($ch, CURLOPT_POSTFIELDS, $param);  
        $response = curl_exec($ch);
        curl_close($ch);
        
        $response = json_decode($response, true);
        return $response;
        
  }
    
}

6.配置qBot.php文件内信息即可
7.ssh进入机器人文件夹下,执行命令php qBot.php,即可运行

使用说明
1.消息处理只给了一个简单例子,自行添加修改即可
2.webscoket库一定要大于等于2.0版本
3.PHP版本要大于等于8.0
4.PHP安装fileinfo拓展

持续运行机器人:
ssh执行命令screen -S qqbot php qBot.php

发表新评论