Rewritten sendMessage function, which is now variadic templated.

Added Boost dependency (for Boost.Optional).
This commit is contained in:
Kirill Kirilenko 2016-11-10 20:37:29 +03:00
parent 9da2b2966e
commit 2ed1d34926
8 changed files with 565 additions and 60 deletions

View file

@ -4,21 +4,30 @@
#include "User.hpp" #include "User.hpp"
#include "Message.hpp" #include "Message.hpp"
#include "Update.hpp" #include "Update.hpp"
#include "SendMessageRequest.hpp"
#include "SendPhotoRequest.hpp"
#include <string> #include <string>
#include <memory> #include <memory>
namespace telebotxx namespace telebotxx
{ {
template <typename RequestType, typename T>
void setRequestOption(RequestType& request, T t)
{
request.setOption(t);
}
template <typename RequestType, typename T, typename... Ts>
void setRequestOption(RequestType& request, T&& t, Ts&&... ts)
{
setRequestOption(request, std::forward<T>(t));
setRequestOption(request, std::forward<Ts>(ts)...);
};
class BotApi class BotApi
{ {
public: public:
enum class ParseMode
{
Plain,
Markdown,
Html
};
/// \param [in] token bot's secret token /// \param [in] token bot's secret token
BotApi(const std::string& token); BotApi(const std::string& token);
@ -30,11 +39,23 @@ namespace telebotxx
User getMe(); User getMe();
/// \brief Send text message /// \brief Send text message
/// \param [in] chat chat identifier /// \param chatId chat identifier
/// \param [in] text message text /// \param text text
/// \param [in] parseMode parse mode
/// \return Message object, recieved from the server /// \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<typename... Ts>
Message sendMessage(ChatId&& chatId, Text&& text, Ts&&... args)
{
SendMessageRequest request(getTelegramMainUrl(), std::forward<ChatId>(chatId), std::forward<Text>(text));
setRequestOption(request, std::forward<Ts>(args)...);
return request.execute();
}
/// \brief Send image /// \brief Send image
/// \param [in] chat chat identifier /// \param [in] chat chat identifier
@ -58,6 +79,8 @@ namespace telebotxx
Updates getUpdates(int offset = 0, unsigned short limit = 0, unsigned timeout = 0); Updates getUpdates(int offset = 0, unsigned short limit = 0, unsigned timeout = 0);
private: private:
std::string getTelegramMainUrl() const;
class Impl; class Impl;
std::unique_ptr<Impl> impl_; std::unique_ptr<Impl> impl_;
}; };

View file

@ -0,0 +1,109 @@
#ifndef TELEBOTXX_REQUEST_OPTIONS_HPP
#define TELEBOTXX_REQUEST_OPTIONS_HPP
#include <string>
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

View file

@ -0,0 +1,38 @@
#ifndef TELEBOTXX_SEND_MESSAGE_REQUEST_HPP
#define TELEBOTXX_SEND_MESSAGE_REQUEST_HPP
#include <telebotxx/RequestOptions.hpp>
#include <telebotxx/Message.hpp>
#include <boost/optional.hpp>
#include <string>
#include <memory>
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> impl_;
};
}
#endif // TELEBOTXX_SEND_MESSAGE_REQUEST_HPP

View file

@ -37,44 +37,6 @@ public:
return *parseUser(doc, "result", REQUIRED); 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<StringBuffer> 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) inline Message sendPhoto(const std::string& chat, const std::string& filename, const std::string& caption)
{ {
auto r = cpr::Post(cpr::Url{telegramMainUrl_ + "/sendPhoto"}, auto r = cpr::Post(cpr::Url{telegramMainUrl_ + "/sendPhoto"},
@ -174,6 +136,11 @@ public:
return *parseUpdates(doc, "result", REQUIRED); return *parseUpdates(doc, "result", REQUIRED);
} }
std::string getTelegramMainUrl() const
{
return telegramMainUrl_;
}
private: private:
std::string token_; std::string token_;
@ -193,9 +160,10 @@ User BotApi::getMe()
return impl_->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>(chatId), std::forward<Text>(text));
return request.execute();
} }
Message BotApi::sendPhoto(const std::string& chat, const std::string& filename, const std::string& caption) 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); return impl_->getUpdates(offset, limit, timeout);
} }
std::string BotApi::getTelegramMainUrl() const
{
return impl_->getTelegramMainUrl();
}

View file

@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 2.8)
message(STATUS "Configuring telebotxx") message(STATUS "Configuring telebotxx")
find_package(Boost REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
# Add required compiler flags # Add required compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
@ -16,6 +19,8 @@ set(SOURCE_FILES Attachment.cpp
Message.cpp Message.cpp
Update.cpp Update.cpp
User.cpp User.cpp
RequestOptions.cpp
SendMessageRequest.cpp
) )
add_library(telebotxx SHARED ${SOURCE_FILES}) add_library(telebotxx SHARED ${SOURCE_FILES})

159
src/RequestOptions.cpp Normal file
View file

@ -0,0 +1,159 @@
#include <telebotxx/RequestOptions.hpp>
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_;
}

173
src/SendMessageRequest.cpp Normal file
View file

@ -0,0 +1,173 @@
#include <telebotxx/SendMessageRequest.hpp>
#include <telebotxx/Logging.hpp>
#include "JsonObjects.hpp"
#include <cpr/cpr.h>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
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<StringBuffer> 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> parseMode_;
boost::optional<DisableWebPagePreview> disableWebPagePreview_;
boost::optional<DisableNotification> disableNotification_;
boost::optional<ReplyTo> replyToMessageId_;
};
SendMessageRequest::SendMessageRequest(const std::string& telegramMainUrl, const ChatId& chat, const Text& text)
: impl_(std::make_unique<Impl>(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();
}

View file

@ -13,6 +13,8 @@ std::unique_ptr<telebotxx::BotApi> bot;
const std::string CONFIG_FILE_NAME = "config.json"; const std::string CONFIG_FILE_NAME = "config.json";
using namespace telebotxx;
std::string token; std::string token;
std::string chat; std::string chat;
std::string photoFile; std::string photoFile;
@ -31,7 +33,7 @@ struct TestConfig
char buffer[2048]; char buffer[2048];
FileReadStream is(pFile, buffer, sizeof(buffer)); FileReadStream is(pFile, buffer, sizeof(buffer));
Document document; rapidjson::Document document;
document.ParseStream<0, UTF8<>, FileReadStream>(is); document.ParseStream<0, UTF8<>, FileReadStream>(is);
if (!document.IsObject() && document.HasParseError()) if (!document.IsObject() && document.HasParseError())
@ -89,32 +91,56 @@ BOOST_AUTO_TEST_SUITE(TestBotApi)
BOOST_AUTO_TEST_CASE(DefaultConstructor) BOOST_AUTO_TEST_CASE(DefaultConstructor)
{ {
PRINT_TESTNAME; 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) BOOST_AUTO_TEST_CASE(SendMessage)
{ {
PRINT_TESTNAME; PRINT_TESTNAME;
BOOST_REQUIRE(bot); 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) BOOST_AUTO_TEST_CASE(SendMessageWithMarkdown)
{ {
PRINT_TESTNAME; PRINT_TESTNAME;
BOOST_REQUIRE(bot); BOOST_REQUIRE(bot);
BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, BOOST_REQUIRE_NO_THROW(bot->sendMessage(ChatId{chat},
"[Sample text in markdown](http://google.com/)", Text{"[Sample text in markdown](http://google.com/)"},
telebotxx::BotApi::ParseMode::Markdown)); ParseMode::Markdown
));
} }
BOOST_AUTO_TEST_CASE(SendMessageWithHtml) BOOST_AUTO_TEST_CASE(SendMessageWithHtml)
{ {
PRINT_TESTNAME; PRINT_TESTNAME;
BOOST_REQUIRE(bot); BOOST_REQUIRE(bot);
BOOST_REQUIRE_NO_THROW(bot->sendMessage(chat, BOOST_REQUIRE_NO_THROW(bot->sendMessage(ChatId{chat},
"<a href=\"http://google.com/\">Sample text in HTML</a>", Text{"<a href=\"http://google.com/\">Sample text in HTML</a>"},
telebotxx::BotApi::ParseMode::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) BOOST_AUTO_TEST_CASE(SendPhoto)
@ -133,7 +159,6 @@ BOOST_AUTO_TEST_SUITE(TestBotApi)
BOOST_AUTO_TEST_CASE(GetUpdates) BOOST_AUTO_TEST_CASE(GetUpdates)
{ {
using namespace telebotxx;
PRINT_TESTNAME; PRINT_TESTNAME;
BOOST_REQUIRE(bot); BOOST_REQUIRE(bot);
Updates updates; Updates updates;