diff --git a/include/telebotxx/BotApi.hpp b/include/telebotxx/BotApi.hpp index 17338cb..df09d6b 100644 --- a/include/telebotxx/BotApi.hpp +++ b/include/telebotxx/BotApi.hpp @@ -4,21 +4,30 @@ #include "User.hpp" #include "Message.hpp" #include "Update.hpp" +#include "SendMessageRequest.hpp" +#include "SendPhotoRequest.hpp" #include #include namespace telebotxx { + template + void setRequestOption(RequestType& request, T t) + { + request.setOption(t); + } + + template + void setRequestOption(RequestType& request, T&& t, Ts&&... ts) + { + setRequestOption(request, std::forward(t)); + setRequestOption(request, std::forward(ts)...); + }; + class BotApi { public: - enum class ParseMode - { - Plain, - Markdown, - Html - }; /// \param [in] token bot's secret token BotApi(const std::string& token); @@ -30,11 +39,23 @@ namespace telebotxx User getMe(); /// \brief Send text message - /// \param [in] chat chat identifier - /// \param [in] text message text - /// \param [in] parseMode parse mode + /// \param chatId chat identifier + /// \param text text /// \return Message object, recieved from the server - Message sendMessage(const std::string& chat, const std::string& text, ParseMode parseMode = ParseMode::Plain); + Message sendMessage(ChatId&& chatId, Text&& text); + + /// \brief Send text message + /// \param chatId chat identifier + /// \param text text + /// \param args parameters + /// \return Message object, recieved from the server + template + Message sendMessage(ChatId&& chatId, Text&& text, Ts&&... args) + { + SendMessageRequest request(getTelegramMainUrl(), std::forward(chatId), std::forward(text)); + setRequestOption(request, std::forward(args)...); + return request.execute(); + } /// \brief Send image /// \param [in] chat chat identifier @@ -58,6 +79,8 @@ namespace telebotxx Updates getUpdates(int offset = 0, unsigned short limit = 0, unsigned timeout = 0); private: + std::string getTelegramMainUrl() const; + class Impl; std::unique_ptr impl_; }; diff --git a/include/telebotxx/RequestOptions.hpp b/include/telebotxx/RequestOptions.hpp new file mode 100644 index 0000000..6a9d677 --- /dev/null +++ b/include/telebotxx/RequestOptions.hpp @@ -0,0 +1,109 @@ +#ifndef TELEBOTXX_REQUEST_OPTIONS_HPP +#define TELEBOTXX_REQUEST_OPTIONS_HPP + +#include + +namespace telebotxx +{ + class ChatId + { + public: + ChatId(int); + ChatId(const std::string&); + ChatId(const ChatId&); + ChatId(ChatId&&); + ~ChatId(); + + enum class Type { Id, Username }; + Type getType() const; + const int getId() const; + const std::string getUsername() const; + private: + Type type_; + union + { + int id_; + std::string username_; + }; + }; + + using Text = std::string; + using Caption = std::string; + + enum class ParseMode + { + Plain, + Markdown, + Html + }; + + class DisableWebPagePreview + { + public: + DisableWebPagePreview(bool disabled = true); + bool value() const; + private: + bool disable_; + }; + + class DisableNotification + { + public: + DisableNotification(bool disabled = true); + bool value() const; + private: + bool disable_; + }; + + class ReplyTo + { + public: + explicit ReplyTo(int id); + int value() const; + private: + int id_; + }; + + class File + { + public: + explicit File(const std::string& filename); + const std::string& getFilename() const; + private: + std::string filename_; + }; + + class Url + { + public: + explicit Url(const std::string& url); + const std::string& getUrl() const; + private: + std::string url_; + }; + + class Photo + { + public: + explicit Photo(int id); + explicit Photo(const File&); + explicit Photo(const Url&); + Photo(const Photo&); + Photo(Photo&&); + ~Photo(); + + enum class Type { File, Url, Id }; + Type getType() const; + + private: + Type type_; + union + { + int id_; + File file_; + Url url_; + }; + }; +} + +#endif // TELEBOTXX_REQUEST_OPTIONS_HPP diff --git a/include/telebotxx/SendMessageRequest.hpp b/include/telebotxx/SendMessageRequest.hpp new file mode 100644 index 0000000..7ffb861 --- /dev/null +++ b/include/telebotxx/SendMessageRequest.hpp @@ -0,0 +1,38 @@ +#ifndef TELEBOTXX_SEND_MESSAGE_REQUEST_HPP +#define TELEBOTXX_SEND_MESSAGE_REQUEST_HPP + +#include +#include + +#include + +#include +#include + +namespace telebotxx +{ + class SendMessageRequest + { + public: + SendMessageRequest(const std::string& telegramMainUrl, const ChatId& chat, const Text& text); + ~SendMessageRequest(); + + void setParseMode(ParseMode mode); + void setDisableWebPagePreview(const DisableWebPagePreview& disableWebPagePreview); + void setDisableNotification(const DisableNotification& disableNotification); + void setReplyToMessageId(const ReplyTo& replyToMessageId); + + void setOption(ParseMode mode); + void setOption(const DisableWebPagePreview& disableWebPagePreview); + void setOption(const DisableNotification& disableNotification); + void setOption(const ReplyTo& replyToMessageId); + + Message execute(); + + private: + class Impl; + std::unique_ptr impl_; + }; +} + +#endif // TELEBOTXX_SEND_MESSAGE_REQUEST_HPP diff --git a/src/BotApi.cpp b/src/BotApi.cpp index 15ad3fe..75ccda5 100644 --- a/src/BotApi.cpp +++ b/src/BotApi.cpp @@ -37,44 +37,6 @@ public: return *parseUser(doc, "result", REQUIRED); } - inline Message sendMessage(const std::string& chat, const std::string& text, ParseMode parseMode) - { - // Construct JSON body - using namespace rapidjson; - StringBuffer s; - Writer writer(s); - - writer.StartObject(); - writer.String("chat_id"); - writer.String(chat.c_str()); - writer.String("text"); - writer.String(text.c_str()); - if (parseMode != ParseMode::Plain) - { - writer.String("parse_mode"); - writer.String((parseMode == ParseMode::Markdown) ? "Markdown" : "HTML"); - } - writer.EndObject(); - - std::string request = s.GetString(); - - auto r = cpr::Post(cpr::Url{telegramMainUrl_ + "/sendMessage"}, - cpr::Header{{"Content-Type", "application/json"}}, - cpr::Body{request} - ); - auto& response = r.text; - - if (debugMode) - std::cout << "Response: " << response << std::endl; - - rapidjson::Document doc; - doc.Parse(response.c_str()); - - /// \todo Parse message - checkResponse(doc); - return *parseMessage(doc, "result", REQUIRED); - } - inline Message sendPhoto(const std::string& chat, const std::string& filename, const std::string& caption) { auto r = cpr::Post(cpr::Url{telegramMainUrl_ + "/sendPhoto"}, @@ -174,6 +136,11 @@ public: return *parseUpdates(doc, "result", REQUIRED); } + std::string getTelegramMainUrl() const + { + return telegramMainUrl_; + } + private: std::string token_; @@ -193,9 +160,10 @@ User BotApi::getMe() return impl_->getMe(); } -Message BotApi::sendMessage(const std::string& chat, const std::string& text, ParseMode parseMode) +Message BotApi::sendMessage(ChatId&& chatId, Text&& text) { - return impl_->sendMessage(chat, text, parseMode); + SendMessageRequest request(getTelegramMainUrl(), std::forward(chatId), std::forward(text)); + return request.execute(); } Message BotApi::sendPhoto(const std::string& chat, const std::string& filename, const std::string& caption) @@ -212,3 +180,8 @@ Updates BotApi::getUpdates(int offset, unsigned short limit, unsigned int timeou { return impl_->getUpdates(offset, limit, timeout); } + +std::string BotApi::getTelegramMainUrl() const +{ + return impl_->getTelegramMainUrl(); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a211430..acb2fdf 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 REQUIRED) +include_directories(${Boost_INCLUDE_DIRS}) + # Add required compiler flags set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") @@ -16,6 +19,8 @@ set(SOURCE_FILES Attachment.cpp Message.cpp Update.cpp User.cpp + RequestOptions.cpp + SendMessageRequest.cpp ) add_library(telebotxx SHARED ${SOURCE_FILES}) diff --git a/src/RequestOptions.cpp b/src/RequestOptions.cpp new file mode 100644 index 0000000..d50f187 --- /dev/null +++ b/src/RequestOptions.cpp @@ -0,0 +1,159 @@ +#include + +using namespace telebotxx; + +ChatId::ChatId(int id) + : type_(Type::Id), id_(id) +{ +} + +ChatId::ChatId(const std::string& str) + : type_(Type::Username), username_(str) +{ +} + +ChatId::ChatId(const ChatId& other) + : type_(other.type_) +{ + if (type_ == Type::Id) + id_ = other.id_; + else + new(&username_) std::string(other.username_); +} + +ChatId::ChatId(ChatId&& other) + : type_(std::move(other.type_)) +{ + if (type_ == Type::Id) + { + id_ = other.id_; + other.id_ = 0; + } + else + { + new(&username_) std::string(std::move(other.username_)); + other.username_.~basic_string(); + } +} + +ChatId::~ChatId() +{ + if (type_ == Type::Username) + username_.~basic_string(); +} + +ChatId::Type ChatId::getType() const +{ + return type_; +} + +const int ChatId::getId() const +{ + return id_; +} + +const std::string ChatId::getUsername() const +{ + return username_; +} + +//////////////////////////////////////////////////////////////// + +DisableWebPagePreview::DisableWebPagePreview(bool disabled) + : disable_(disabled) +{ +} + +bool DisableWebPagePreview::value() const +{ + return disable_; +} + +//////////////////////////////////////////////////////////////// + +DisableNotification::DisableNotification(bool disabled) + : disable_(disabled) +{ +} + +bool DisableNotification::value() const +{ + return disable_; +} + +//////////////////////////////////////////////////////////////// + +ReplyTo::ReplyTo(int id) + : id_(id) +{ +} + +int ReplyTo::value() const +{ + return id_; +} + +//////////////////////////////////////////////////////////////// + +File::File(const std::string& filename) + : filename_(filename) +{ +} + +const std::string& File::getFilename() const +{ + return filename_; +} + +//////////////////////////////////////////////////////////////// + +const std::string& Url::getUrl() const +{ + return url_; +} + +Url::Url(const std::string& url) + : url_(url) +{ +} + +//////////////////////////////////////////////////////////////// + +Photo::Photo(int id) + : type_(Type::Id), id_(id) +{ +} + +Photo::Photo(const File& file) + : type_(Type::File), file_(file) +{ +} + +Photo::Photo(const Url& url) + : type_(Type::Url), url_(url) +{ +} + +Photo::Photo(const Photo& other) + : type_(other.type_) +{ + if (type_ == Type::Id) + id_ = other.id_; + else if (type_ == Type::File) + new(&file_) File(other.file_); + else + new(&url_) Url(other.url_); +} + +Photo::~Photo() +{ + if (type_ == Type::File) + file_.~File(); + else if(type_ == Type::Url) + url_.~Url(); +} + +Photo::Type Photo::getType() const +{ + return type_; +} \ No newline at end of file diff --git a/src/SendMessageRequest.cpp b/src/SendMessageRequest.cpp new file mode 100644 index 0000000..868096c --- /dev/null +++ b/src/SendMessageRequest.cpp @@ -0,0 +1,173 @@ +#include +#include +#include "JsonObjects.hpp" + +#include +#include +#include + +using namespace telebotxx; + +class SendMessageRequest::Impl +{ +public: + Impl(const std::string& telegramMainUrl, const ChatId& chat, const Text& text) + : telegramMainUrl_(telegramMainUrl), chatId_(chat), text_(text) + { + } + + void setParseMode(ParseMode mode) + { + parseMode_ = mode; + } + + void setDisableWebPagePreview(const DisableWebPagePreview& disableWebPagePreview) + { + disableWebPagePreview_ = disableWebPagePreview; + } + + void setDisableNotification(const DisableNotification& disableNotification) + { + disableNotification_ = disableNotification; + } + + void setReplyToMessageId(const ReplyTo& replyToMessageId) + { + replyToMessageId_ = replyToMessageId; + } + + Message execute() + { + // Construct JSON body + using namespace rapidjson; + StringBuffer s; + Writer writer(s); + + writer.StartObject(); + + // Add chat_id + writer.String("chat_id"); + if (chatId_.getType() == ChatId::Type::Id) + writer.Int(chatId_.getId()); + else + writer.String(chatId_.getUsername().c_str()); + + writer.String("text"); + writer.String(text_.c_str()); + + // Add parse_mode + if (parseMode_) + { + writer.String("parse_mode"); + writer.String((parseMode_ == ParseMode::Markdown) ? "Markdown" : (parseMode_ == ParseMode::Html) ? "HTML" : "Plain"); + } + + // Add disable_web_page_preview + if (disableWebPagePreview_) + { + writer.String("disable_web_page_preview"); + writer.Bool(disableWebPagePreview_->value()); + } + + // Add disable_notification + if (disableNotification_) + { + writer.String("disable_notification"); + writer.Bool(disableNotification_->value()); + } + + // Add reply_to_message_id + if (replyToMessageId_) + { + writer.String("reply_to_message_id"); + writer.Int(replyToMessageId_->value()); + } + + /// \todo: Add reply_markup + + writer.EndObject(); + + // Execute POST and get response + std::string request = s.GetString(); + if (debugMode) + std::cout << "Request: " << request << std::endl; + auto r = cpr::Post(cpr::Url{telegramMainUrl_ + "/sendMessage"}, + cpr::Header{{"Content-Type", "application/json"}}, + cpr::Body{request} + ); + auto& response = r.text; + + if (debugMode) + std::cout << "Response: " << response << std::endl; + + rapidjson::Document doc; + doc.Parse(response.c_str()); + + /// \todo Parse message + checkResponse(doc); + return *parseMessage(doc, "result", REQUIRED); + } + +private: + std::string telegramMainUrl_; + ChatId chatId_; + Text text_; + boost::optional parseMode_; + boost::optional disableWebPagePreview_; + boost::optional disableNotification_; + boost::optional replyToMessageId_; +}; + +SendMessageRequest::SendMessageRequest(const std::string& telegramMainUrl, const ChatId& chat, const Text& text) + : impl_(std::make_unique(telegramMainUrl, chat, text)) +{ +} + +SendMessageRequest::~SendMessageRequest() +{ +} + +void SendMessageRequest::setParseMode(ParseMode mode) +{ + impl_->setParseMode(mode); +} + +void SendMessageRequest::setDisableWebPagePreview(const DisableWebPagePreview& disableWebPagePreview) +{ + impl_->setDisableWebPagePreview(disableWebPagePreview); +} + +void SendMessageRequest::setDisableNotification(const DisableNotification& disableNotification) +{ + impl_->setDisableNotification(disableNotification); +} + +void SendMessageRequest::setReplyToMessageId(const ReplyTo& replyToMessageId) +{ + impl_->setReplyToMessageId(replyToMessageId); +} + +void SendMessageRequest::setOption(ParseMode mode) +{ + setParseMode(mode); +} + +void SendMessageRequest::setOption(const DisableWebPagePreview& disableWebPagePreview) +{ + setDisableWebPagePreview(disableWebPagePreview); +} + +void SendMessageRequest::setOption(const DisableNotification& disableNotification) +{ + setDisableNotification(disableNotification); +} + +void SendMessageRequest::setOption(const ReplyTo& replyToMessageId) +{ + setReplyToMessageId(replyToMessageId); +} + +Message SendMessageRequest::execute() +{ + return impl_->execute(); +} diff --git a/tests/TestApi.cpp b/tests/TestApi.cpp index 312d266..45da09e 100644 --- a/tests/TestApi.cpp +++ b/tests/TestApi.cpp @@ -13,6 +13,8 @@ std::unique_ptr bot; const std::string CONFIG_FILE_NAME = "config.json"; +using namespace telebotxx; + std::string token; std::string chat; std::string photoFile; @@ -31,7 +33,7 @@ struct TestConfig char buffer[2048]; FileReadStream is(pFile, buffer, sizeof(buffer)); - Document document; + rapidjson::Document document; document.ParseStream<0, UTF8<>, FileReadStream>(is); if (!document.IsObject() && document.HasParseError()) @@ -89,32 +91,56 @@ BOOST_AUTO_TEST_SUITE(TestBotApi) BOOST_AUTO_TEST_CASE(DefaultConstructor) { PRINT_TESTNAME; - BOOST_REQUIRE_NO_THROW(bot.reset(new telebotxx::BotApi(token))); + BOOST_REQUIRE_NO_THROW(bot.reset(new BotApi(token))); } BOOST_AUTO_TEST_CASE(SendMessage) { PRINT_TESTNAME; BOOST_REQUIRE(bot); - BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, "Sample text")); + BOOST_REQUIRE_NO_THROW(bot->sendMessage(ChatId{chat}, + Text{"Sample text"} + )); } BOOST_AUTO_TEST_CASE(SendMessageWithMarkdown) { PRINT_TESTNAME; BOOST_REQUIRE(bot); - BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, - "[Sample text in markdown](http://google.com/)", - telebotxx::BotApi::ParseMode::Markdown)); + BOOST_REQUIRE_NO_THROW(bot->sendMessage(ChatId{chat}, + Text{"[Sample text in markdown](http://google.com/)"}, + ParseMode::Markdown + )); } BOOST_AUTO_TEST_CASE(SendMessageWithHtml) { PRINT_TESTNAME; BOOST_REQUIRE(bot); - BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, - "Sample text in HTML", - telebotxx::BotApi::ParseMode::Html)); + BOOST_REQUIRE_NO_THROW(bot->sendMessage(ChatId{chat}, + Text{"Sample text in HTML"}, + ParseMode::Html + )); + } + + BOOST_AUTO_TEST_CASE(SendMessageWithoutPreview) + { + PRINT_TESTNAME; + BOOST_REQUIRE(bot); + BOOST_REQUIRE_NO_THROW(bot->sendMessage(ChatId{chat}, + Text{"http://google.com/"}, + DisableWebPagePreview() + )); + } + + BOOST_AUTO_TEST_CASE(SendMessageWithoutNotification) + { + PRINT_TESTNAME; + BOOST_REQUIRE(bot); + BOOST_REQUIRE_NO_THROW(bot->sendMessage(ChatId{chat}, + Text{"Message without notification"}, + DisableNotification() + )); } BOOST_AUTO_TEST_CASE(SendPhoto) @@ -133,7 +159,6 @@ BOOST_AUTO_TEST_SUITE(TestBotApi) BOOST_AUTO_TEST_CASE(GetUpdates) { - using namespace telebotxx; PRINT_TESTNAME; BOOST_REQUIRE(bot); Updates updates;