利用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.被动回复消息都不限量
介绍
使用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