1. 程式人生 > >QQ聊天機器人--基於酷Q寫的外掛

QQ聊天機器人--基於酷Q寫的外掛

  閒著無聊,百度了一下,在微信上調戲微軟小冰,感覺很有趣,於是乎百度了一系列關於自動回覆的,最後得知了,圖靈機器人和酷Q這兩個軟體,在找的時候發現酷Q(基於易語言)有C++的sdk,所以就打算藉助酷Q,自己寫一個會聊天的機器人,本文中將記錄碰到的問題以及解決方式。

(具體程式碼見我的碼雲)

一、準備工作

二、工程目錄

  工程目錄大致這樣:
這裡寫圖片描述
  其中有幾個是我後面加的標頭檔案,其中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")
//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; }; };

  因為初階段的目標很小,所以我這樣寫了,定義一個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;
    }
}

——————————————-以下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檔案,用法也很簡單。

————————–先寫到這吧,後面再補充修改