博客和更新地址:QQ聊天机器人实例

前言

此文章为PHP实现基于Mirai的QQ机器人的一部分。

申请API

该聊天机器人需要使用聊天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返回处理结果。

至此,聊天机器人部署完毕。

版权声明:本文为alsaces原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/alsaces/p/14578731.html