QQ聊天机器人--基于酷Q写的插件

  闲着无聊,百度了一下,在微信上调戏微软小冰,感觉很有趣,于是乎百度了一系列关于自动回复的,最后得知了,图灵机器人和酷Q这两个软件,在找的时候发现酷Q(基于易语言)有C++的sdk,所以就打算借助酷Q,自己写一个会聊天的机器人,本文中将记录碰到的问题以及解决方式。

(具体代码见我的码云)

一、准备工作

  首先,去酷Q官网下一个酷Q Air,然后然后去下载酷Q SDK FOR C++:
https://d.cqp.me/Pro/%E5%BC%80%E5%8F%91/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8#VC.2B.2B_SDK
  然后呢,上面的网址介绍了一些关于酷Q的设置,以及如何使用我们自己根据SDK写的插件,或者说是动态库。

二、工程目录

  工程目录大致这样:
这里写图片描述
  其中有几个是我后面加的头文件,其中cqp.h是酷Q提供的接口,我们可以调用里面的接口给私聊或者群发送消息。

三、目标

  一开始一脑子就想着做像微软小冰那样的自动回复,思考了挺久的,想字符串比较,按概率来选定字符串回复了,感觉那样做会需要很多很多的字符串的搭建,于是乎打算先从小的开始,做一个报天气的,结果如下:
这里写图片描述

  那么,做一个回复天气的,问题就来了,也就是,我们要怎么获取天气?

四、动手__回复天气功能

  为了及时的获取天气信息,我们就必须借助socket或者可以定时从电脑上保存天气数据然后读取输出,而在不知道天气API接口的情况下,通过请求一个天气网站,获取代码,然后用正则表达式去匹配出我们想要的数据,若在知道API的情况下,如代码中所用的是https://www.seniverse.com/免费的api,通过请求获得的json数据,解析,得到我们想要的数据。(我也是才知道json的用处orz)。

//stdafx.h
#include "stdint.h"
#include "string"
#include "cqp.h"

#include<list>
#include<vector>
#include<time.h>
#include <regex>
#include<fstream>
#include <WinSock2.h>
#include <stdio.h>
#include"json\json.h"

#pragma comment(lib, "ws2_32")
#pragma comment(lib, "json_vc71_libmtd")
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
//my_robot.h
class qq_pool {
private:
    std::list<int64_t> qq_id_list;
public:
    bool inside_pool(int64_t qq);
    void add_qq(int64_t qq);
};

class Robot {
public:
    Robot(int val = 0) :order(val) {}
    void run(int64_t qq,const char *msg);
private:
    int order;
    qq_pool m_pool;
    struct Knowlege {
        std::string Q;
        std::vector<std::string> A;
    };
};
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

  因为初阶段的目标很小,所以我这样写了,定义一个run()用于在私聊的时候接受消息进行反馈,也就是说,我的机器人的行为都是在run()里面执行,而qq_pool是记录了聊天的qq,如果是第一次对话,则有一段固定的消息,如上面QQ对话所示的第一次见面问话。

//my_robot.cpp
#include "stdafx.h"
#include "my_robot.h"
#include "my_socket.h"


extern int ac;//调用酷Q接口必要的一个值

//是否在qq列表中
bool qq_pool::inside_pool(int64_t qq)
{
    for (auto i : qq_id_list)
        if (i == qq)
            return true;
    return false;
}
//天气,这部分代码写得比较烂,也没怎么整理
std::string tianqi()
{
    //用于请求数据,获取html的代码
    clientSoket socket_tmp;
    socket_tmp.Connect("www.weather.com.cn");
    std::string str = socket_tmp.Get("http://www.weather.com.cn/weather1d/101260512.shtml#dingzhi_first");
    socket_tmp.Close();
    //从html数据正则表达式匹配-雷山天气
    std::regex ruler1("<input type=\"hidden\" id=\"hidden_title\" value=(\.\*) />");
    std::smatch mat1;
    std::regex_search(str, mat1, ruler1);
    std::string temp;
    if (std::regex_search(str, mat1, ruler1))
        temp = "雷山的天气: " + mat1.str(1);
    //匹配温度
    std::regex ruler2("(\\d+)/(\\d+)");
    std::smatch mat2;
    //计算温差
    if (std::regex_search(str, mat2, ruler2))
    {
        int Wd = atoi(mat1.str(1).c_str()) - atoi(mat1.str(2).c_str());
        if (abs(Wd) > 6)
            temp += "  [CQ:emoji,id=128556]昼夜温差较大,请多加衣服";
        else if (atoi(mat1.str().c_str()) < 15)
            temp += "白天较冷,不宜出门。。。[CQ:emoji,id=127770]";
        else
            temp += "[CQ:emoji,id=128566]太热了,和小A寝室吹空调吧。";
    }
    temp += "\n";
    //武汉天气,通过API获得json数据,可查看json数据知道我们所要的键-值数据,如"name"="武汉"
    clientSoket socket_tmp2;
    socket_tmp2.Connect("api.seniverse.com");
    std::string str2 = socket_tmp2.Get("https://api.seniverse.com/v3/weather/now.json?key=nvpjpsvbbxbtlcld&location=WT3Q0FW9ZJ3Q&language=zh-Hans&unit=c");
    socket_tmp2.Close();
    Json::Reader reader;
    Json::Value json_ob;
    if (!reader.parse(str2, json_ob))
        return "天气API报错:*_*";
    temp+="Acer现在在"+json_ob["results"][0]["location"]["name"].asString()+",";
    temp+="天气:"+json_ob["results"][0]["now"]["text"].asString()+",";
    if (atoi(json_ob["results"][0]["now"]["temperature"].asString().c_str()) < 12)
        temp += "温度:" + json_ob["results"][0]["now"]["temperature"].asString() + "今天有点冷哦[CQ:emoji,id=128542].";
    else
        temp += "温度:" + json_ob["results"][0]["now"]["temperature"].asString();
    return temp;
}
void qq_pool::add_qq(int64_t qq)
{
    qq_id_list.push_back(qq);
}

void Robot::run(int64_t qq,const char *msg)
{
    if (!m_pool.inside_pool(qq))
    {
        std::string temp = "   第一次见面!你好,我是Acer的回复机器人,你可以叫我小A,=_=\n   窝现在还不太聪明,所以有啥事请输入一下序号回复:\n        1、查看Acer家的天气与Acer所在处天气\n        2、我有事找你,请快联系我\n        3、小A和聊天";
        m_pool.add_qq(qq);
        CQ_sendPrivateMsg(ac, qq, temp.c_str());
        return;
    }
    //回复...
    std::string temp;
    std::string send[3] = { "嗨呀,不知道你在说什么。。。","What happend?!!","就不能按照第一次见面说的去做么!!Orz" };
    //随机数
    srand((unsigned)time(NULL));
    int m_sjs = rand() % 3;
    switch (msg[0])
    {
    case '1':
        temp+=tianqi();
        CQ_sendPrivateMsg(ac, qq, temp.c_str());
        break;
    case '2':
        temp = "   主人在外面溜达,来不及回复。。。";
        CQ_sendPrivateMsg(ac, qq, temp.c_str());
        break;
    case '3':
        temp = "   Acer不让我和你聊天|·ω·`) ";
        CQ_sendPrivateMsg(ac, qq, temp.c_str());
        break;
    default:
        CQ_sendPrivateMsg(ac, qq, send[m_sjs].c_str());
        break;
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

——————————————-以下my_socket的实现————————————-

//my_socket.h
#pragma once
#include"stdafx.h"
class clientSoket {
private:
//member
    WSADATA wsdata;
    sockaddr_in serveraddr;
    int sock;

public:
    char* U2G(const char* utf8);
    clientSoket();
    ~clientSoket();
    void Connect(const char *hostname);
    void Close();
    std::string Get(const char *gethtml);
};
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
//my_socket.cpp
#include "stdafx.h"
#include "my_socket.h"
#define MAX_BUFF_SIZE 40960
#define MAX_GET_LENGHT 1000

//UTF转换GBK编码,因为大多网页都是UTF-8,接受到的时候里面的中文读取会是乱码
char * clientSoket::U2G(const char * utf8)
{
    int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
    wchar_t* wstr = new wchar_t[len + 1];
    memset(wstr, 0, len + 1);
    MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
    len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
    char* str = new char[len + 1];
    memset(str, 0, len + 1);
    WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
    if (wstr) delete[] wstr;
    return str;
}

//get请求+地址
std::string clientSoket::Get(const char * gethtml)
{
    char temp[MAX_GET_LENGHT];
    int Len = 0;
    sprintf(temp, "GET %s\r\n", gethtml);
    if (send(sock, temp, strlen(temp), 0) < 0) {
        closesocket(sock);
        return nullptr;
    }
    char buff[MAX_BUFF_SIZE] = {};
    std::string HtmlData;

    FILE *fp;
    fp = fopen("htmldata.txt", "w");
    char *m_ptr;
    /* 开始接收数据 */
    while ((Len = recv(sock, buff, MAX_BUFF_SIZE, 0)) > 0) {
        m_ptr = U2G(buff);
        fwrite(m_ptr, 1, Len, fp);
        HtmlData += m_ptr;
        delete[] m_ptr;
    }
    fclose(fp);
    return HtmlData;
}

clientSoket::clientSoket()
{
    sock = socket(AF_INET, SOCK_STREAM, 0);
}

clientSoket::~clientSoket()
{
    /*closesocket(sock);
    WSACleanup();*/
}

void clientSoket::Connect(const char *hostname)
{
    hostent* host = gethostbyname(hostname);
    /* 初始化一个连接服务器的结构体 */
    sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(80);

    /* 此处也可以不用这么做,不需要用gethostbyname,把网址ping一下,得出IP也是可以的 */
    serveraddr.sin_addr.S_un.S_addr = *((int*)*host->h_addr_list);
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
            closesocket(sock);
            return;
    }

    if (connect(sock, (struct sockaddr*)&serveraddr, sizeof(sockaddr_in)) == -1) {
        closesocket(sock);
        return;
    }
}

void clientSoket::Close()
{
    closesocket(sock);
    WSACleanup();
}


  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

四、问题以及如何解决

  在上面,我们用到了一些关于socket编程与计算机网络知识,还有json数据如何解析,对于json数据,能用库解析更好,也可以自己用正则表达式匹配,在上面使用了jsoncpp。
  关于HTTP如何请求格式:http://blog.csdn.net/a19881029/article/details/14002273
  如何使用jsoncpp:
    1、首先,从jsoncpp的github里下载:https://github.com/open-source-parsers/jsoncpp
    2、编译该项目,会生成一个lib文件:
这里写图片描述
    3、我用的是VS2015,通过导入静态库的方法,加载lib文件(如最上面的stdafx.h的写法)
    4、设置这个(要不然会报错什么什么位置不对):
这里写图片描述
    5、导入json.h使用(上面的代码是完整的已经导入了)
    6、jsoncpp的使用方法百度,上面我们只是使用解析json文件,用法也很简单。

————————–先写到这吧,后面再补充修改

        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
            </div>

猜你喜欢

转载自blog.csdn.net/yzj_0722/article/details/80245219