QQ聊天机器人实例
博客和更新地址:QQ聊天机器人实例
前言
此文章为PHP实现基于Mirai的QQ机器人的一部分。
申请API
该聊天机器人需要使用聊天API,通过将机器人接收到的消息转发给该接口,接口响应消息,来实现聊天机器人的效果。
免费的聊天API有:
其中图灵机器人需要实名认证,并且免费额度100/天,听说会更加智能(没感觉)。
腾讯AI开放平台的智能闲聊API接口调用额度无限制,响应速度快。
青云客似乎也是无限制。但是感觉大厂会稳点,所以选用的腾讯AI开放平台的智能闲聊API接口服务。
QQ登录腾讯智能闲聊,点击应用管理->创建应用,应用类型选机器人,其他的随意,创建完成后保存得到的APPID和APPKEY,在后面会使用。
实例
在Web服务器监听目录下新建文件Bot.class.php(以下代码需要PHP7以上),内容为
<?php
namespace Mirai;
use Exception;
/**
* Mirai QQ机器人
* @param string $_url MiraiHTTP接口地址
* @param string $_authKey Mirai认证Key
* @param string $_sessionKey Mirai会话Key
*/
class Bot
{
private $_url;
private $_authKey;
private $_sessionKey;
/**
* 构造函数
* @param string $url MiraiHTTP接口地址
* @param string $authKey Mirai认证Key
*/
function __construct(string $url, string $authKey)
{
$this->_url = $url;
$this->_authKey = array(\'authKey\' => $authKey);
}
/**
* cURL获取数据
* @param string $url 发送请求的链接
* @param int $ifPost 是否为post请求(1||0)
* @param mixed $postFields post的数据
* @param string $cookie 发送请求携带的cookie
* @param mixed $cookieFile cookie文件
* @param int $ifHeader 是否获取响应头信息(1||0)
* @throws Exception 请求失败
* @return mixed 响应结果
*/
public function httpRequest(string $url, int $ifPost = 0, $postFields = \'\', string $cookie = \'\', $cookieFile = \'\', int $ifHeader = 0)
{
// 模拟http请求header头
$header = array(
"Connection: Keep-Alive",
"Accept: text/html, application/xhtml+xml, */*",
"Pragma: no-cache",
"Accept-Language: zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3",
"User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"
);
// 初始化一个cURL会话
$ch = curl_init();
// 设置cURL传输选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, $ifHeader);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
$ifPost && curl_setopt($ch, CURLOPT_POST, $ifPost);
$ifPost && curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$cookie && curl_setopt($ch, CURLOPT_COOKIE, $cookie); // 发送cookie变量
$cookieFile && curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile); // 发送cookie文件
$cookieFile && curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile); // 写入cookie到文件
curl_setopt($ch, CURLOPT_TIMEOUT, 60); // 允许执行的最长秒数
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 执行cURL会话
$result = curl_exec($ch);
// 失败则抛出异常
if ($result === false) {
throw new Exception(\'Sending request to \' . $url . \' failed!\');
}
// 关闭 cURL 会话
curl_close($ch);
// 释放$ch
unset($ch);
return $result;
}
/**
* 进行认证
* @throws Exception 认证失败
* @return bool
*/
public function auth(): bool
{
$url = $this->_url . \'/auth\';
$postData = json_encode($this->_authKey);
$response = json_decode($this->httpRequest($url, 1, $postData));
if ($response !== false && $response->code === 0) {
$this->_sessionKey = $response->session;
return true;
} else {
throw new Exception(\'Mirai authentication failed!\');
}
}
/**
* 校验Session
* @param int $qq Session将要绑定的Bot的qq号
* @throws Exception 校验Session失败
* @return bool
*/
public function verify(int $qq): bool
{
$url = $this->_url . \'/verify\';
$postData = json_encode(
array(
\'sessionKey\' => $this->_sessionKey,
\'qq\' => $qq
)
);
$response = json_decode($this->httpRequest($url, 1, $postData));
if ($response !== false && $response->code === 0) {
return true;
} else {
throw new Exception(\'Validation session failed!\' . \'The qq is \' . $qq . \'.\');
}
}
/**
* 释放Session
* @param int $qq 与该Session绑定Bot的QQ号码
* @throws Exception 释放Session失败
* @return bool
*/
public function release(int $qq): bool
{
$url = $this->_url . \'/release\';
$postData = json_encode(
array(
\'sessionKey\' => $this->_sessionKey,
\'qq\' => $qq
)
);
$response = json_decode($this->httpRequest($url, 1, $postData));
if ($response !== false && $response->code === 0) {
return true;
} else {
throw new Exception(\'Failed to release session! The qq is \' . $qq . \'!\');
}
}
/**
* 发送好友消息
* @param int $qq 发送消息目标好友的QQ号
* @param array $messageChain 消息链,消息对象构成的数组
* @param int $quote 回复消息的messageId
* @throws Exception 发送好友消息失败
* @return int messageId 可引用进行回复
*/
public function sendFriendMessage(int $qq, array $messageChain, $quote = null): int
{
$url = $this->_url . \'/sendFriendMessage\';
$postData = json_encode(
array(
\'sessionKey\' => $this->_sessionKey,
\'target\' => $qq,
\'quote\' => $quote,
\'messageChain\' => $messageChain
)
);
$response = json_decode($this->httpRequest($url, 1, $postData));
if ($response !== false && $response->code === 0) {
return $response->messageId;
} else {
throw new Exception(\'Failed to send friend message to qq:\' . $qq . \'!\');
}
}
}
该类的方法实现了mirai-api-http所提供功能的一部分,包括认证身份、校验Session、释放Session和发送好友消息,重要参数和功能已在注释中说明。
然后在同目录下新建文件Chatter.class.php,内容为
<?php
namespace Mirai;
use Exception;
/**
* 聊天机器人
* @param string $_url 接口地址
* @param string $_appId 应用标识(AppId)
* @param string $_appKey 应用Key(AppKey)
* @param int $_session 会话标识(应用内唯一)
*/
class Chatter
{
private $_url;
private $_appId;
private $_appKey;
private $_session;
/**
* 构造函数
* @param string $appId 应用标识(AppId)
* @param string $appKey 应用Key(AppKey)
* @param int $session 会话标识(应用内唯一)
*/
function __construct(string $appId, string $appKey, int $session = 23333)
{
$this->_url = \'https://api.ai.qq.com/fcgi-bin/nlp/nlp_textchat\';
$this->_appId = $appId;
$this->_appKey = $appKey;
$this->_session = $session;
}
/**
* 根据接口请求参数和应用密钥计算请求签名
* @param array $params 接口请求数组
* @return string 签名结果
*/
public function getReqSign(array $params): string
{
// 字典升序排序
ksort($params);
// 拼按URL键值对
$str = \'\';
foreach ($params as $key => $value) {
if ($value !== \'\') {
$str .= $key . \'=\' . urlencode($value) . \'&\';
}
}
// 拼接app_key
$str .= \'app_key=\' . $this->_appKey;
// MD5运算+转换大写,得到请求签名
$sign = strtoupper(md5($str));
return $sign;
}
/**
* 执行POST请求,并取回响应结果
* @param array $params 完整接口请求数组
* @return mixed 返回false表示失败,否则表示API成功返回的HTTP BODY部分
*/
public function doHttpPost(array $params)
{
$curl = curl_init();
$response = false;
do {
// 设置HTTP URL (API地址)
curl_setopt($curl, CURLOPT_URL, $this->_url);
// 设置HTTP HEADER (表单POST)
$head = array(
\'Content-Type: application/x-www-form-urlencoded\'
);
curl_setopt($curl, CURLOPT_HTTPHEADER, $head);
// 设置HTTP BODY (URL键值对)
$body = http_build_query($params);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
// 调用API,获取响应结果
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_NOBODY, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($curl);
if ($response === false) {
$response = false;
break;
}
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($code != 200) {
$response = false;
break;
}
} while (0);
curl_close($curl);
return $response;
}
/**
* 获取聊天信息
* @param string $chatMessage 发送给机器人接口的消息
* @return object 机器人返回的结果
*/
public function chat(string $chatMessage): object
{
$postData = array(
\'app_id\' => $this->_appId,
\'session\' => $this->_session,
\'question\' => $chatMessage,
\'time_stamp\' => strval(time()),
\'nonce_str\' => strval(rand()),
\'sign\' => \'\'
);
$postData[\'sign\'] = $this->getReqSign($postData);
$response = json_decode($this->doHttpPost($postData));
if ($response->ret === 0) {
return $response->data;
} else {
throw new Exception(\'Failed to get chat message!\');
}
}
}
该类的方法实现了腾讯智能闲聊API的接口鉴权和消息请求功能,重要参数和功能已在注释中说明。
之后在同目录下新建文件index.php,内容为
<?php
namespace Mirai;
use Exception;
// 包含Bot和Chatter类
include_once("./Bot.class.php");
include_once("./Chatter.class.php");
// 本地调试模拟请求(与获取请求Body不能同时存在)
// $friendMessage = \'{"type":"FriendMessage","messageChain":[{"type":"Source","id":16959,"time":1616033525},{"type":"Plain","text":"你好"}],"sender":{"id":消息发送者QQ,"nickname":"消息发送者昵称","remark":""}}\';
// $message = json_decode($friendMessage);
// 获取请求的Body(服务器部署时使用,与"获取本地调试模拟请求"不能同时存在)
$message = json_decode(file_get_contents(\'php://input\'));
// 判断是否为好友文本消息
if ($message->type === \'FriendMessage\' && $message->messageChain[1]->type === \'Plain\') {
// 实例化一个chatter
$chatter = new Chatter(\'应用标识(AppId)\', \'应用Key(AppKey)\');
// 获得消息响应
try {
$responseMessage = $chatter->chat($message->messageChain[1]->text);
} catch (Exception $e) {
// 本地调试输出错误信息
// echo $e->getMessage() . \' At file:\' . $e->getFile() . \' on line:\' . $e->getLine() . \'.\';
// 发生异常时记录日志
error_log($e->getMessage() . \' At file:\' . $e->getFile() . \' on line:\' . $e->getLine() . \'.\');
}
// 实例化一个bot
$bot = new Bot(\'MiraiHTTPAPI地址\', \'Mirai认证Key\');
// 机器人QQ号
$botQQ = xxxxxxxxxx;
// 消息链
$messageChain = array(
array(
\'type\' => \'Plain\',
\'text\' => $responseMessage->answer
)
);
// 发送消息的目标QQ
$targetQQ = $message->sender->id;
try {
// 进行认证
$bot->auth();
// 校验Session
$bot->verify($botQQ);
// 发送该消息
$bot->sendFriendMessage($targetQQ, $messageChain);
// 释放Session
$bot->release($botQQ);
} catch (Exception $e) {
// 本地调试输出错误信息
// echo $e->getMessage().\' At file:\'.$e->getFile().\' on line:\'.$e->getLine().\'.\';
// 发生异常时记录日志
error_log($e->getMessage() . \' At file:\' . $e->getFile() . \' on line:\' . $e->getLine() . \'.\');
}
}
以上代码获取了mirai-api-http上报的消息,并对消息类型进行了判断,通过后将消息转发到聊天机器人API,之后将获取的响应结果发送给目标QQ好友,就实现了与机器人(智能)聊天的效果。
部署到服务器
将该机器人部署到服务器,首先新建一个动态站点,将以上文件放入服务器端的Web服务器监听目录下,并将mirai-api-http配置文件中的”上报URL”改为文件所在站点的url。
例如使用nginx创建动态站点api.alsaces.cn,并将以上文件放入”站点目录/qqbot/v1/”下,将mirai-api-http配置文件中的上报URL改为{http://api.alsaces.cn/qqbot/v1/},重启mirai。
当机器人收到消息时便会上报至该url,PHP处理接收的消息,合法则向mirai-api-http返回处理结果。
至此,聊天机器人部署完毕。