闲着无聊,百度了一下,在微信上调戏微软小冰,感觉很有趣,于是乎百度了一系列关于自动回复的,最后得知了,图灵机器人和酷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
#include
#include
#include
#include
#include
#include
#include"json\json.h"
#pragma comment(lib, "ws2_32")
#pragma comment(lib, "json_vc71_libmtd")
//my_robot.h
class qq_pool {
private:
std::list 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;
};
};
因为初阶段的目标很小,所以我这样写了,定义一个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("");
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;
}
}
——————————————-以下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);
};
//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();
}
在上面,我们用到了一些关于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文件,用法也很简单。
————————–先写到这吧,后面再补充修改