Basic response parsing implemented.

Fixed typo.
This commit is contained in:
Kirill Kirilenko 2016-09-26 23:07:14 +03:00
parent 96394c3e0d
commit f58208f864
6 changed files with 168 additions and 12 deletions

View file

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
project(tetebotxx CXX) project(telebotxx CXX)
option (TELEBOTXX_BUILD_TESTS "Build unit tests using Boost.Test" ON) option (TELEBOTXX_BUILD_TESTS "Build unit tests using Boost.Test" ON)
option (TELEBOTXX_GENERATE_DOC "Generate API documentation with Doxygen" ON) option (TELEBOTXX_GENERATE_DOC "Generate API documentation with Doxygen" ON)

View file

@ -1,6 +1,8 @@
#ifndef TELEBOTXX_BOTAPI_H #ifndef TELEBOTXX_BOTAPI_H
#define TELEBOTXX_BOTAPI_H #define TELEBOTXX_BOTAPI_H
#include "User.hpp"
#include <string> #include <string>
#include <memory> #include <memory>
@ -14,6 +16,10 @@ namespace telebotxx
~BotApi(); ~BotApi();
/// \brief Get basic information about the bot
/// \return User object with information about the bot
User getMe();
/// \brief Send text message /// \brief Send text message
/// \param [in] chat chat identifier /// \param [in] chat chat identifier
/// \param [in] text message text /// \param [in] text message text

View file

@ -0,0 +1,36 @@
#ifndef TELEBOTXX_EXCEPTION_HPP
#define TELEBOTXX_EXCEPTION_HPP
#include <stdexcept>
namespace telebotxx
{
class ParseError : public std::invalid_argument
{
public:
ParseError(const std::string& message)
: std::invalid_argument(message)
{
}
};
class ApiError : public std::runtime_error
{
public:
ApiError(int code, const std::string& message)
: std::runtime_error(message), code_(code)
{
}
int getCode() const
{
return code_;
}
protected:
int code_;
};
}
#endif // TELEBOTXX_EXCEPTION_HPP

View file

@ -1,15 +1,82 @@
#include <telebotxx/BotApi.hpp> #include <telebotxx/BotApi.hpp>
#include <telebotxx/Exception.hpp>
#include <sstream> #include <sstream>
#include <rapidjson/document.h>
#include <rapidjson/writer.h> #include <rapidjson/writer.h>
#include <rapidjson/istreamwrapper.h>
#include <curlpp/cURLpp.hpp> #include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp> #include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp> #include <curlpp/Options.hpp>
#include <boost/log/trivial.hpp>
using namespace telebotxx; using namespace telebotxx;
const rapidjson::Value& parseResponse(const rapidjson::Document& doc)
{
using namespace rapidjson;
if (!doc.IsObject())
throw ParseError("Object expected");
// Get status
if (!doc.HasMember("ok") || !doc["ok"].IsBool())
throw ParseError("Field 'ok' not found or has invalid type");
bool ok = doc["ok"].GetBool();
if (ok)
{
if (!doc.HasMember("result") || !doc["result"].IsObject())
throw ParseError("Field 'result' not found or has invalid type");
return doc["result"];
}
else
{
if (!doc.HasMember("error_code") || !doc["error_code"].IsInt())
throw ParseError("Field 'error_code' not found or has invalid type");
int code = doc["error_code"].GetInt();
if (!doc.HasMember("description") || !doc["description"].IsString())
throw ParseError("Field 'description' not found or has invalid type");
std::string description(doc["description"].GetString());
throw ApiError(code, description);
}
}
User parseUser(const rapidjson::Value& obj)
{
if (!obj.HasMember("id") || !obj["id"].IsInt())
throw ParseError("Field 'id' not found or has invalid type");
int id = obj["id"].GetInt();
if (!obj.HasMember("first_name") || !obj["first_name"].IsString())
throw ParseError("Field 'first_name' not found or has invalid type");
std::string firstName(obj["first_name"].GetString());
std::string lastName;
if (obj.HasMember("last_name"))
{
if (obj["last_name"].IsString())
lastName = obj["last_name"].GetString();
else
throw ParseError("Field 'last_name' has invalid type");
}
std::string username;
if (obj.HasMember("username"))
{
if (obj["username"].IsString())
username = obj["username"].GetString();
else
throw ParseError("Field 'username' has invalid type");
}
return User(id, firstName, lastName, username);
}
class BotApi::Impl class BotApi::Impl
{ {
public: public:
@ -17,8 +84,27 @@ public:
: token_(token) : token_(token)
{ {
telegramMainUrl_ = "https://api.telegram.org/bot" + token_; telegramMainUrl_ = "https://api.telegram.org/bot" + token_;
botUser_ = getMe();
}
/// \todo run getMe command to check token inline User getMe()
{
curlpp::Easy request;
std::stringstream ss;
request.setOpt(new curlpp::Options::Url(telegramMainUrl_ + "/getMe"));
request.setOpt(new curlpp::Options::Verbose(false));
request.setOpt(new curlpp::options::WriteStream(&ss));
request.perform();
BOOST_LOG_TRIVIAL(debug) << ss.str();
using namespace rapidjson;
IStreamWrapper isw(ss);
Document doc;
doc.ParseStream(isw);
return parseUser(parseResponse(doc));
} }
inline void sendMessage(const std::string& chat, const std::string& text) inline void sendMessage(const std::string& chat, const std::string& text)
@ -35,9 +121,11 @@ public:
writer.String(text.c_str()); writer.String(text.c_str());
writer.EndObject(); writer.EndObject();
std::istringstream myStream(s.GetString()); std::istringstream requestStream(s.GetString());
std::cout << myStream.str() << std::endl; BOOST_LOG_TRIVIAL(debug) << requestStream.str();
auto size = myStream.str().size(); auto size = requestStream.str().size();
std::stringstream responseStream;
// Construct HTTP request // Construct HTTP request
curlpp::Easy request; curlpp::Easy request;
@ -47,20 +135,33 @@ public:
headers.push_back("Content-Type: application/json"); headers.push_back("Content-Type: application/json");
// Content-Length // Content-Length
std::ostringstream ss; {
ss << "Content-Length: " << size; std::ostringstream ss;
headers.push_back(ss.str()); ss << "Content-Length: " << size;
headers.push_back(ss.str());
}
// Set options // Set options
request.setOpt(new curlpp::Options::Url(telegramMainUrl_ + "/sendMessage")); request.setOpt(new curlpp::Options::Url(telegramMainUrl_ + "/sendMessage"));
request.setOpt(new curlpp::Options::Verbose(true)); request.setOpt(new curlpp::Options::Verbose(false));
request.setOpt(new curlpp::Options::ReadStream(&myStream)); request.setOpt(new curlpp::Options::ReadStream(&requestStream));
request.setOpt(new curlpp::options::WriteStream(&responseStream));
request.setOpt(new curlpp::Options::InfileSize(size)); request.setOpt(new curlpp::Options::InfileSize(size));
request.setOpt(new curlpp::options::HttpHeader(headers)); request.setOpt(new curlpp::options::HttpHeader(headers));
request.setOpt(new curlpp::Options::Post(true)); request.setOpt(new curlpp::Options::Post(true));
// Perform request // Perform request
request.perform(); request.perform();
BOOST_LOG_TRIVIAL(debug) << responseStream.str();
using namespace rapidjson;
IStreamWrapper isw(responseStream);
Document doc;
doc.ParseStream(isw);
/// \todo Parse message
parseResponse(doc);
} }
inline void sendPhoto(const std::string& chat, const std::istream& file, const std::string& caption) inline void sendPhoto(const std::string& chat, const std::istream& file, const std::string& caption)
@ -80,7 +181,7 @@ public:
// Set options // Set options
request.setOpt(new curlpp::Options::Url(telegramMainUrl_ + "/sendPhoto")); request.setOpt(new curlpp::Options::Url(telegramMainUrl_ + "/sendPhoto"));
request.setOpt(new curlpp::Options::Verbose(true)); request.setOpt(new curlpp::Options::Verbose(false));
request.setOpt(new curlpp::options::HttpHeader(headers)); request.setOpt(new curlpp::options::HttpHeader(headers));
request.setOpt(new curlpp::Options::Post(true)); request.setOpt(new curlpp::Options::Post(true));
@ -90,8 +191,11 @@ public:
private: private:
std::string token_; std::string token_;
std::string telegramMainUrl_; std::string telegramMainUrl_;
User botUser_;
}; };
BotApi::BotApi(const std::string& token) BotApi::BotApi(const std::string& token)
@ -101,6 +205,11 @@ BotApi::BotApi(const std::string& token)
BotApi::~BotApi() = default; BotApi::~BotApi() = default;
User BotApi::getMe()
{
return impl_->getMe();
}
void BotApi::sendMessage(const std::string& chat, const std::string& text) void BotApi::sendMessage(const std::string& chat, const std::string& text)
{ {
return impl_->sendMessage(chat, text); return impl_->sendMessage(chat, text);

View file

@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 2.8)
message(STATUS "Configuring telebotxx") message(STATUS "Configuring telebotxx")
find_package(Boost 1.54 REQUIRED log system)
include_directories(${Boost_INCLUDE_DIRS})
find_package(CURLpp REQUIRED) find_package(CURLpp REQUIRED)
include_directories(${CURLPP_INCLUDE_DIRS}) include_directories(${CURLPP_INCLUDE_DIRS})
@ -22,4 +25,5 @@ set(SOURCE_FILES BotApi.cpp
) )
add_library(telebotxx SHARED ${SOURCE_FILES}) add_library(telebotxx SHARED ${SOURCE_FILES})
target_link_libraries(telebotxx curlpp ${CURL_LIBRARIES}) target_link_libraries(telebotxx curlpp ${Boost_LIBRARIES} ${CURL_LIBRARIES})
set_property(TARGET telebotxx APPEND PROPERTY COMPILE_DEFINITIONS BOOST_LOG_DYN_LINK)

View file

@ -82,6 +82,7 @@ BOOST_AUTO_TEST_SUITE(TestBotApi)
BOOST_AUTO_TEST_CASE(SendMessage) BOOST_AUTO_TEST_CASE(SendMessage)
{ {
PRINT_TESTNAME; PRINT_TESTNAME;
BOOST_REQUIRE(bot);
BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, "Hello from C++!")); BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, "Hello from C++!"));
} }