From f58208f8641876c271245d5a6fa0beefb02eab3e Mon Sep 17 00:00:00 2001 From: Kirill Kirilenko Date: Mon, 26 Sep 2016 23:07:14 +0300 Subject: [PATCH] Basic response parsing implemented. Fixed typo. --- CMakeLists.txt | 2 +- include/telebotxx/BotApi.hpp | 6 ++ include/telebotxx/Exception.hpp | 36 +++++++++ src/BotApi.cpp | 129 +++++++++++++++++++++++++++++--- src/CMakeLists.txt | 6 +- tests/TestApi.cpp | 1 + 6 files changed, 168 insertions(+), 12 deletions(-) create mode 100644 include/telebotxx/Exception.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0013aac..85d2a9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8) 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_GENERATE_DOC "Generate API documentation with Doxygen" ON) diff --git a/include/telebotxx/BotApi.hpp b/include/telebotxx/BotApi.hpp index 57cfc47..e93052b 100644 --- a/include/telebotxx/BotApi.hpp +++ b/include/telebotxx/BotApi.hpp @@ -1,6 +1,8 @@ #ifndef TELEBOTXX_BOTAPI_H #define TELEBOTXX_BOTAPI_H +#include "User.hpp" + #include #include @@ -14,6 +16,10 @@ namespace telebotxx ~BotApi(); + /// \brief Get basic information about the bot + /// \return User object with information about the bot + User getMe(); + /// \brief Send text message /// \param [in] chat chat identifier /// \param [in] text message text diff --git a/include/telebotxx/Exception.hpp b/include/telebotxx/Exception.hpp new file mode 100644 index 0000000..2f72ff8 --- /dev/null +++ b/include/telebotxx/Exception.hpp @@ -0,0 +1,36 @@ +#ifndef TELEBOTXX_EXCEPTION_HPP +#define TELEBOTXX_EXCEPTION_HPP + +#include + +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 diff --git a/src/BotApi.cpp b/src/BotApi.cpp index 4955539..9930c53 100644 --- a/src/BotApi.cpp +++ b/src/BotApi.cpp @@ -1,15 +1,82 @@ #include +#include #include +#include #include +#include #include #include #include +#include + 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 { public: @@ -17,8 +84,27 @@ public: : token_(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) @@ -35,9 +121,11 @@ public: writer.String(text.c_str()); writer.EndObject(); - std::istringstream myStream(s.GetString()); - std::cout << myStream.str() << std::endl; - auto size = myStream.str().size(); + std::istringstream requestStream(s.GetString()); + BOOST_LOG_TRIVIAL(debug) << requestStream.str(); + auto size = requestStream.str().size(); + + std::stringstream responseStream; // Construct HTTP request curlpp::Easy request; @@ -47,20 +135,33 @@ public: headers.push_back("Content-Type: application/json"); // Content-Length - std::ostringstream ss; - ss << "Content-Length: " << size; - headers.push_back(ss.str()); + { + std::ostringstream ss; + ss << "Content-Length: " << size; + headers.push_back(ss.str()); + } // Set options request.setOpt(new curlpp::Options::Url(telegramMainUrl_ + "/sendMessage")); - request.setOpt(new curlpp::Options::Verbose(true)); - request.setOpt(new curlpp::Options::ReadStream(&myStream)); + request.setOpt(new curlpp::Options::Verbose(false)); + 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::HttpHeader(headers)); request.setOpt(new curlpp::Options::Post(true)); // Perform request 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) @@ -80,7 +181,7 @@ public: // Set options 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::Post(true)); @@ -90,8 +191,11 @@ public: private: + + std::string token_; std::string telegramMainUrl_; + User botUser_; }; BotApi::BotApi(const std::string& token) @@ -101,6 +205,11 @@ BotApi::BotApi(const std::string& token) BotApi::~BotApi() = default; +User BotApi::getMe() +{ + return impl_->getMe(); +} + void BotApi::sendMessage(const std::string& chat, const std::string& text) { return impl_->sendMessage(chat, text); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0671e1b..7774100 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 2.8) message(STATUS "Configuring telebotxx") +find_package(Boost 1.54 REQUIRED log system) +include_directories(${Boost_INCLUDE_DIRS}) + find_package(CURLpp REQUIRED) include_directories(${CURLPP_INCLUDE_DIRS}) @@ -22,4 +25,5 @@ set(SOURCE_FILES BotApi.cpp ) 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) diff --git a/tests/TestApi.cpp b/tests/TestApi.cpp index 404f1ff..e282087 100644 --- a/tests/TestApi.cpp +++ b/tests/TestApi.cpp @@ -82,6 +82,7 @@ BOOST_AUTO_TEST_SUITE(TestBotApi) BOOST_AUTO_TEST_CASE(SendMessage) { PRINT_TESTNAME; + BOOST_REQUIRE(bot); BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, "Hello from C++!")); }