summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt24
-rw-r--r--samples/echobot/src/main.cpp18
-rw-r--r--src/tgbot/Api.cpp207
-rw-r--r--src/tgbot/Api.h10
-rw-r--r--src/tgbot/Bot.h43
-rw-r--r--src/tgbot/EventBroadcaster.h96
-rw-r--r--src/tgbot/EventHandler.h (renamed from src/tgbot/EventManager.cpp)44
-rw-r--r--src/tgbot/EventManager.h75
-rw-r--r--src/tgbot/Http.cpp172
-rw-r--r--src/tgbot/Http.h125
-rw-r--r--src/tgbot/TgTypeParser.cpp (renamed from src/tgbot/Parser.cpp)77
-rw-r--r--src/tgbot/TgTypeParser.h (renamed from src/tgbot/Parser.h)20
-rw-r--r--src/tgbot/net/HttpClient.cpp71
-rw-r--r--src/tgbot/net/HttpClient.h49
-rw-r--r--src/tgbot/net/HttpParser.cpp204
-rw-r--r--src/tgbot/net/HttpParser.h68
-rw-r--r--src/tgbot/net/HttpReqArg.h50
-rw-r--r--src/tgbot/net/HttpServer.h93
-rw-r--r--src/tgbot/net/TgLongPoll.cpp (renamed from src/tgbot/Bot.cpp)25
-rw-r--r--src/tgbot/net/TgLongPoll.h48
-rw-r--r--src/tgbot/net/TgWebhookLocalServer.h47
-rw-r--r--src/tgbot/net/TgWebhookServer.h57
-rw-r--r--src/tgbot/net/TgWebhookTcpServer.h46
-rw-r--r--src/tgbot/net/Url.cpp (renamed from src/tgbot/Url.cpp)17
-rw-r--r--src/tgbot/net/Url.h (renamed from src/tgbot/Url.h)1
-rw-r--r--src/tgbot/tools/StringTools.cpp31
-rw-r--r--src/tgbot/tools/StringTools.h1
-rw-r--r--test/CMakeLists.txt12
-rw-r--r--test/main.cpp26
-rw-r--r--test/tgbot/net/HttpParser.cpp136
-rw-r--r--test/tgbot/net/Url.cpp58
-rw-r--r--test/tgbot/tools/StringTools.cpp63
-rw-r--r--test/utils.cpp67
-rw-r--r--test/utils.h83
34 files changed, 1540 insertions, 624 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a625dd8..2949165 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,19 +2,20 @@ cmake_minimum_required(VERSION 3.0)
project(TgBot)
### options
-option(ENABLE_TESTS "Set to ON to enable building of tests and samples" ON)
+option(ENABLE_TESTS "Set to ON to enable building of tests" OFF)
+option(ENABLE_SAMPLES "Set to ON to enable building of samples" OFF)
### sources
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
include_directories("${PROJECT_SOURCE_DIR}/src")
set(SRC_LIST
- src/tgbot/Bot.cpp
src/tgbot/Api.cpp
- src/tgbot/EventManager.cpp
- src/tgbot/Http.cpp
- src/tgbot/Url.cpp
- src/tgbot/Parser.cpp
+ src/tgbot/TgTypeParser.cpp
src/tgbot/TgException.cpp
+ src/tgbot/net/Url.cpp
+ src/tgbot/net/HttpClient.cpp
+ src/tgbot/net/HttpParser.cpp
+ src/tgbot/net/TgLongPoll.cpp
src/tgbot/tools/StringTools.cpp
)
@@ -25,7 +26,7 @@ include_directories(${OPENSSL_INCLUDE_DIR})
# boost
set(Boost_USE_MULTITHREADED ON)
-find_package(Boost COMPONENTS system container iostreams REQUIRED)
+find_package(Boost COMPONENTS system container iostreams unit_test_framework REQUIRED)
include_directories(${Boost_INCLUDE_DIR})
set(LIB_LIST
@@ -39,6 +40,13 @@ target_link_libraries(${PROJECT_NAME} ${LIB_LIST})
### tests
if (ENABLE_TESTS)
- message(STATUS "Building of tests and sambles is enabled")
+ message(STATUS "Building of tests is enabled")
+ enable_testing()
+ add_subdirectory(test)
+endif()
+
+### samples
+if (ENABLE_SAMPLES)
+ message(STATUS "Building of sambles is enabled")
add_subdirectory(samples)
endif()
diff --git a/samples/echobot/src/main.cpp b/samples/echobot/src/main.cpp
index 4874aab..03d9d3d 100644
--- a/samples/echobot/src/main.cpp
+++ b/samples/echobot/src/main.cpp
@@ -25,8 +25,9 @@
#include <exception>
#include <tgbot/Bot.h>
-#include <tgbot/tools/StringTools.h>
+#include <tgbot/net/TgLongPoll.h>
#include <tgbot/TgException.h>
+#include <tgbot/tools/StringTools.h>
using namespace std;
using namespace TgBot;
@@ -39,28 +40,31 @@ int main() {
sigintGot = true;
});
- Bot bot("PLACE_YOUR_TOKEN_HERE");
- bot.getEvents().onCommand("start", [](Message::Ptr message, Bot* const bot) {
- bot->getApi().sendMessage(message->chat->id, "Hi!");
+ Bot bot("100743144:AAEzJJSWYezyUYlwgzSjf0-5hdokGyUjbXM");
+ bot.getEvents().onCommand("start", [&bot](Message::Ptr message) {
+ bot.getApi().sendMessage(message->chat->id, "Hi!");
});
- bot.getEvents().onAnyMessage([](Message::Ptr message, Bot* const bot) {
+ bot.getEvents().onAnyMessage([&bot](Message::Ptr message) {
printf("User wrote %s\n", message->text.c_str());
if (StringTools::startsWith(message->text, "/start")) {
return;
}
- bot->getApi().sendMessage(message->chat->id, "Your message is: " + message->text);
+ bot.getApi().sendMessage(message->chat->id, "Your message is: " + message->text);
});
+ printf("try {\n");
try {
printf("Bot username: %s\n", bot.getApi().getMe()->username.c_str());
+ TgLongPoll longPoll(bot);
while (!sigintGot) {
printf("Long poll started\n");
- bot.startLongPoll();
+ longPoll.start();
}
} catch (TgException& e) {
printf("error: %s\n", e.what());
}
+ printf("}\n");
return 0;
}
diff --git a/src/tgbot/Api.cpp b/src/tgbot/Api.cpp
index 9dd7314..8da88c8 100644
--- a/src/tgbot/Api.cpp
+++ b/src/tgbot/Api.cpp
@@ -22,241 +22,240 @@
#include "Api.h"
-#include <boost/property_tree/json_parser.hpp>
-
-#include "tgbot/Bot.h"
+#include "tgbot/TgTypeParser.h"
#include "tgbot/TgException.h"
+#include "tgbot/net/HttpClient.h"
using namespace std;
using namespace boost::property_tree;
namespace TgBot {
-Api::Api(Bot* const bot) : _bot(bot) {
+Api::Api(const std::string& token) : _token(token) {
}
User::Ptr Api::getMe() const {
- return _bot->getParser().parseUser(sendRequest("getMe").find("result")->second);
+ return TgTypeParser::getInstance().parseUser(sendRequest("getMe").find("result")->second);
}
Message::Ptr Api::sendMessage(int32_t chatId, const string& text, bool disableWebPagePreview, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("text", text));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("text", text));
if (disableWebPagePreview) {
- args.push_back(Http::Argument("disable_web_page_preview", disableWebPagePreview));
+ args.push_back(HttpReqArg("disable_web_page_preview", disableWebPagePreview));
}
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendMessage", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendMessage", args).find("result")->second);
}
Message::Ptr Api::forwardMessage(int32_t chatId, int32_t fromChatId, int32_t messageId) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("from_chat_id", fromChatId));
- args.push_back(Http::Argument("message_id", messageId));
- return _bot->getParser().parseMessage(sendRequest("forwardMessage", args).find("result")->second);
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("from_chat_id", fromChatId));
+ args.push_back(HttpReqArg("message_id", messageId));
+ return TgTypeParser::getInstance().parseMessage(sendRequest("forwardMessage", args).find("result")->second);
}
Message::Ptr Api::sendPhoto(int32_t chatId, const InputFile::Ptr& photo, const string& caption, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("photo", photo->data, true, photo->mimeType));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("photo", photo->data, true, photo->mimeType));
if (!caption.empty()) {
- args.push_back(Http::Argument("caption", caption));
+ args.push_back(HttpReqArg("caption", caption));
}
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendPhoto", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendPhoto", args).find("result")->second);
}
Message::Ptr Api::sendPhoto(int32_t chatId, const string& photo, const string& caption, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("photo", photo));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("photo", photo));
if (!caption.empty()) {
- args.push_back(Http::Argument("caption", caption));
+ args.push_back(HttpReqArg("caption", caption));
}
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendPhoto", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendPhoto", args).find("result")->second);
}
Message::Ptr Api::sendAudio(int32_t chatId, const InputFile::Ptr& audio, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("audio", audio->data, true, audio->mimeType));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("audio", audio->data, true, audio->mimeType));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendAudio", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendAudio", args).find("result")->second);
}
Message::Ptr Api::sendAudio(int32_t chatId, const string& audio, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("audio", audio));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("audio", audio));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendAudio", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendAudio", args).find("result")->second);
}
Message::Ptr Api::sendDocument(int32_t chatId, const InputFile::Ptr& document, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("document", document->data, true, document->mimeType));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("document", document->data, true, document->mimeType));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendDocument", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendDocument", args).find("result")->second);
}
Message::Ptr Api::sendDocument(int32_t chatId, const string& document, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("document", document));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("document", document));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendDocument", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendDocument", args).find("result")->second);
}
Message::Ptr Api::sendSticker(int32_t chatId, const InputFile::Ptr& sticker, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("sticker", sticker->data, true, sticker->mimeType));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("sticker", sticker->data, true, sticker->mimeType));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendSticker", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendSticker", args).find("result")->second);
}
Message::Ptr Api::sendSticker(int32_t chatId, const string& sticker, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("sticker", sticker));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("sticker", sticker));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendSticker", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendSticker", args).find("result")->second);
}
Message::Ptr Api::sendVideo(int32_t chatId, const InputFile::Ptr& video, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("video", video->data, true, video->mimeType));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("video", video->data, true, video->mimeType));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendVideo", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendVideo", args).find("result")->second);
}
Message::Ptr Api::sendVideo(int32_t chatId, const string& video, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("video", video));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("video", video));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendVideo", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendVideo", args).find("result")->second);
}
Message::Ptr Api::sendLocation(int32_t chatId, float latitude, float longitude, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("latitude", latitude));
- args.push_back(Http::Argument("longitude", longitude));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("latitude", latitude));
+ args.push_back(HttpReqArg("longitude", longitude));
if (replyToMessageId) {
- args.push_back(Http::Argument("reply_to_message_id", replyToMessageId));
+ args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId));
}
if (replyMarkup) {
- args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup)));
+ args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup)));
}
- return _bot->getParser().parseMessage(sendRequest("sendLocation", args).find("result")->second);
+ return TgTypeParser::getInstance().parseMessage(sendRequest("sendLocation", args).find("result")->second);
}
void Api::sendChatAction(int32_t chatId, const string& action) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("chat_id", chatId));
- args.push_back(Http::Argument("action", action));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("chat_id", chatId));
+ args.push_back(HttpReqArg("action", action));
sendRequest("sendChatAction", args);
}
UserProfilePhotos::Ptr Api::getUserProfilePhotos(int32_t userId, int32_t offset, int32_t limit) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("user_id", userId));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("user_id", userId));
if (offset) {
- args.push_back(Http::Argument("offset", offset));
+ args.push_back(HttpReqArg("offset", offset));
}
limit = max(1, min(100, limit));
- args.push_back(Http::Argument("limit", limit));
- return _bot->getParser().parseUserProfilePhotos(sendRequest("getUserProfilePhotos", args).find("result")->second);
+ args.push_back(HttpReqArg("limit", limit));
+ return TgTypeParser::getInstance().parseUserProfilePhotos(sendRequest("getUserProfilePhotos", args).find("result")->second);
}
vector<Update::Ptr> Api::getUpdates(int32_t offset, int32_t limit, int32_t timeout) const {
- vector<Http::Argument> args;
+ vector<HttpReqArg> args;
if (offset) {
- args.push_back(Http::Argument("offset", offset));
+ args.push_back(HttpReqArg("offset", offset));
}
limit = max(1, min(100, limit));
- args.push_back(Http::Argument("limit", limit));
+ args.push_back(HttpReqArg("limit", limit));
if (timeout) {
- args.push_back(Http::Argument("timeout", timeout));
+ args.push_back(HttpReqArg("timeout", timeout));
}
- return _bot->getParser().parseArray<Update>(_bot->getParser().parseUpdate, sendRequest("getUpdates", args), "result");
+ return TgTypeParser::getInstance().parseArray<Update>(TgTypeParser::getInstance().parseUpdate, sendRequest("getUpdates", args), "result");
}
void Api::setWebhook(const string& url) const {
- vector<Http::Argument> args;
- args.push_back(Http::Argument("url", url));
+ vector<HttpReqArg> args;
+ args.push_back(HttpReqArg("url", url));
sendRequest("setWebhook", args);
}
-boost::property_tree::ptree Api::sendRequest(const std::string& method, const std::vector<Http::Argument>& args) const {
+boost::property_tree::ptree Api::sendRequest(const std::string& method, const std::vector<HttpReqArg>& args) const {
std::string url = "https://api.telegram.org/bot";
- url += _bot->getToken();
+ url += _token;
url += "/";
url += method;
try {
- ptree result = _bot->getParser().parseJson(_bot->getHttp().makeRequest(url, args));
+ ptree result = TgTypeParser::getInstance().parseJson(HttpClient::getInstance().makeRequest(url, args));
if (result.get<bool>("ok")) {
return result;
} else {
diff --git a/src/tgbot/Api.h b/src/tgbot/Api.h
index c10fe56..384de55 100644
--- a/src/tgbot/Api.h
+++ b/src/tgbot/Api.h
@@ -28,7 +28,7 @@
#include <boost/property_tree/ptree.hpp>
-#include "tgbot/Http.h"
+#include "tgbot/net/HttpReqArg.h"
#include "tgbot/types/User.h"
#include "tgbot/types/Message.h"
#include "tgbot/types/GenericReply.h"
@@ -45,6 +45,8 @@ class Api {
friend Bot;
public:
+ Api(const std::string& token);
+
User::Ptr getMe() const;
Message::Ptr sendMessage(int32_t chatId, const std::string& text, bool disableWebPagePreview = false, int32_t replyToMessageId = 0, const GenericReply::Ptr& replyMarkup = GenericReply::Ptr()) const;
Message::Ptr forwardMessage(int32_t chatId, int32_t fromChatId, int32_t messageId) const;
@@ -65,11 +67,9 @@ public:
void setWebhook(const std::string& url = "") const;
private:
- explicit Api(Bot* const bot);
-
- boost::property_tree::ptree sendRequest(const std::string& method, const std::vector<Http::Argument>& args = std::vector<Http::Argument>()) const;
+ boost::property_tree::ptree sendRequest(const std::string& method, const std::vector<HttpReqArg>& args = std::vector<HttpReqArg>()) const;
- Bot* const _bot;
+ const std::string _token;
};
}
diff --git a/src/tgbot/Bot.h b/src/tgbot/Bot.h
index 488ceb1..eeef27a 100644
--- a/src/tgbot/Bot.h
+++ b/src/tgbot/Bot.h
@@ -26,25 +26,15 @@
#include <string>
#include "tgbot/Api.h"
-#include "tgbot/EventManager.h"
-#include "tgbot/Http.h"
-#include "tgbot/Parser.h"
+#include "tgbot/EventBroadcaster.h"
+#include "tgbot/EventHandler.h"
namespace TgBot {
class Bot {
public:
- explicit Bot(const std::string& token);
-
- void startLongPoll();
-
- inline void startServer(unsigned short port) {
- _http.startServer(port, _webhooksServerHandler);
- }
-
- inline void startServer(const std::string& unixSocketPath) {
- _http.startServer(unixSocketPath, _webhooksServerHandler);
+ explicit Bot(const std::string& token) : _token(token), _api(token), _eventHandler(&_eventBroadcaster) {
}
inline const std::string& getToken() const {
@@ -55,34 +45,19 @@ public:
return _api;
}
- inline const EventManager& getEvents() const {
- return _events;
- }
-
- inline EventManager& getEvents() {
- return _events;
- }
-
- inline const Http& getHttp() const {
- return _http;
- }
-
- inline Http& getHttp() {
- return _http;
+ inline EventBroadcaster& getEvents() {
+ return _eventBroadcaster;
}
- inline const Parser& getParser() const {
- return _parser;
+ inline const EventHandler& getEventHandler() const {
+ return _eventHandler;
}
private:
const std::string _token;
const Api _api;
- EventManager _events;
- Http _http;
- const Parser _parser;
- const Http::ServerHandler _webhooksServerHandler;
- int32_t _lastUpdateId = 0;
+ EventBroadcaster _eventBroadcaster;
+ const EventHandler _eventHandler;
};
}
diff --git a/src/tgbot/EventBroadcaster.h b/src/tgbot/EventBroadcaster.h
new file mode 100644
index 0000000..a6074e8
--- /dev/null
+++ b/src/tgbot/EventBroadcaster.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_EVENTBROADCASTER_H
+#define TGBOT_EVENTBROADCASTER_H
+
+#include <string>
+#include <functional>
+#include <vector>
+#include <map>
+
+#include "tgbot/types/Message.h"
+
+namespace TgBot {
+
+class EventHandler;
+
+class EventBroadcaster {
+
+friend EventHandler;
+
+public:
+ typedef std::function<void (const Message::Ptr&)> MessageListener;
+
+ inline void onAnyMessage(const MessageListener& listener) {
+ _onAnyMessageListeners.push_back(listener);
+ }
+
+ inline void onCommand(const std::string& commandName, const MessageListener& listener) {
+ _onCommandListeners[commandName] = listener;
+ }
+
+ inline void onUnknownCommand(const MessageListener& listener) {
+ _onUnknownCommandListeners.push_back(listener);
+ }
+
+ inline void onNonCommandMessage(const MessageListener& listener) {
+ _onNonCommandMessageListeners.push_back(listener);
+ }
+
+private:
+ inline void broadcastAnyMessage(const Message::Ptr& message) const {
+ for (const MessageListener& item : _onAnyMessageListeners) {
+ item(message);
+ }
+ }
+
+ inline bool broadcastCommand(const std::string command, const Message::Ptr& message) const {
+ std::map<std::string, MessageListener>::const_iterator iter = _onCommandListeners.find(command);
+ if (iter == _onCommandListeners.end()) {
+ return false;
+ }
+ iter->second(message);
+ return true;
+ }
+
+ inline void broadcastUnknownCommand(const Message::Ptr& message) const {
+ for (const MessageListener& item : _onUnknownCommandListeners) {
+ item(message);
+ }
+ }
+
+ inline void broadcastNonCommandMessage(const Message::Ptr& message) const {
+ for (const MessageListener& item : _onNonCommandMessageListeners) {
+ item(message);
+ }
+ }
+
+ std::vector<MessageListener> _onAnyMessageListeners;
+ std::map<std::string, MessageListener> _onCommandListeners;
+ std::vector<MessageListener> _onUnknownCommandListeners;
+ std::vector<MessageListener> _onNonCommandMessageListeners;
+};
+
+}
+
+#endif //TGBOT_EVENTBROADCASTER_H
diff --git a/src/tgbot/EventManager.cpp b/src/tgbot/EventHandler.h
index fdd4859..abcf925 100644
--- a/src/tgbot/EventManager.cpp
+++ b/src/tgbot/EventHandler.h
@@ -20,37 +20,37 @@
* SOFTWARE.
*/
-#include "EventManager.h"
+#ifndef TGBOT_EVENTHANDLER_H
+#define TGBOT_EVENTHANDLER_H
+#include "tgbot/EventBroadcaster.h"
+#include "tgbot/types/Update.h"
#include "tgbot/tools/StringTools.h"
-using namespace std;
-
namespace TgBot {
-EventManager::EventManager(Bot* const bot) : _bot(bot) {
-}
+class EventHandler {
-void EventManager::handleUpdate(const Update::Ptr& update) {
- for (EventManager::Listener& item : _onAnyMessageListeners) {
- item(update->message, _bot);
+public:
+ explicit EventHandler(const EventBroadcaster* broadcaster) : _broadcaster(broadcaster) {
}
- if (StringTools::startsWith(update->message->text, "/")) {
- string command = update->message->text.substr(1, update->message->text.find(' ') - 2);
- for (pair<const string, Listener>& item : _onCommandListeners) {
- if (item.first == command) {
- item.second(update->message, _bot);
- return;
+
+ inline void handleUpdate(const Update::Ptr& update) const {
+ _broadcaster->broadcastAnyMessage(update->message);
+ if (StringTools::startsWith(update->message->text, "/")) {
+ std::string command = update->message->text.substr(1, update->message->text.find(' ') - 2);
+ if (!_broadcaster->broadcastCommand(command, update->message)) {
+ _broadcaster->broadcastUnknownCommand(update->message);
}
- }
- for (EventManager::Listener& item : _onUnknownCommandListeners) {
- item(update->message, _bot);
- }
- } else {
- for (EventManager::Listener& item : _onNonCommandMessageListeners) {
- item(update->message, _bot);
+ } else {
+ _broadcaster->broadcastNonCommandMessage(update->message);
}
}
-}
+
+private:
+ const EventBroadcaster* _broadcaster;
+};
}
+
+#endif //TGBOT_EVENTHANDLER_H
diff --git a/src/tgbot/EventManager.h b/src/tgbot/EventManager.h
deleted file mode 100644
index cfe0b04..0000000
--- a/src/tgbot/EventManager.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2015 Oleg Morozenkov
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#ifndef TGBOT_CPP_EVENTMANAGER_H
-#define TGBOT_CPP_EVENTMANAGER_H
-
-#include <string>
-#include <functional>
-#include <vector>
-#include <map>
-
-#include "tgbot/types/Update.h"
-#include "tgbot/types/Message.h"
-
-namespace TgBot {
-
-class Bot;
-
-class EventManager {
-
-friend Bot;
-
-public:
- typedef std::function<void (const Message::Ptr&, Bot* const)> Listener;
-
- inline void onAnyMessage(const Listener& listener) {
- _onAnyMessageListeners.push_back(listener);
- }
-
- inline void onCommand(const std::string& commandName, const Listener& listener) {
- _onCommandListeners[commandName] = listener;
- }
-
- inline void onUnknownCommand(const Listener& listener) {
- _onUnknownCommandListeners.push_back(listener);
- }
-
- inline void onNonCommandMessage(const Listener& listener) {
- _onNonCommandMessageListeners.push_back(listener);
- }
-
-private:
- explicit EventManager(Bot* const bot);
-
- void handleUpdate(const Update::Ptr& update);
-
- Bot* const _bot;
- std::vector<Listener> _onAnyMessageListeners;
- std::map<std::string, Listener> _onCommandListeners;
- std::vector<Listener> _onUnknownCommandListeners;
- std::vector<Listener> _onNonCommandMessageListeners;
-};
-
-}
-
-#endif //TGBOT_CPP_EVENTMANAGER_H
diff --git a/src/tgbot/Http.cpp b/src/tgbot/Http.cpp
deleted file mode 100644
index d512e88..0000000
--- a/src/tgbot/Http.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (c) 2015 Oleg Morozenkov
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include "Http.h"
-
-#include <iostream>
-
-#include <boost/asio/ssl.hpp>
-
-#include "tgbot/tools/StringTools.h"
-
-using namespace std;
-using namespace boost::asio;
-using namespace boost::asio::ip;
-using namespace boost::asio::local;
-using boost::lexical_cast;
-
-namespace TgBot {
-
-string Http::makeRequest(const Url& url, const vector<Argument>& args) {
- string result;
-
- ssl::context context(ssl::context::sslv23);
- context.set_default_verify_paths();
-
- ssl::stream<tcp::socket> socket(_ioService, context);
- tcp::resolver resolver(_ioService);
- tcp::resolver::query query(url.host, url.protocol);
- connect(socket.lowest_layer(), resolver.resolve(query));
-
- socket.set_verify_mode(ssl::verify_none);
- socket.set_verify_callback(ssl::rfc2818_verification(url.host));
- socket.handshake(ssl::stream<tcp::socket>::client);
-
- string requestText;
- if (args.empty()) {
- requestText += "GET ";
- } else {
- requestText += "POST ";
- }
- requestText += url.path;
- requestText += url.query.empty() ? "" : "?" + url.query;
- requestText += " HTTP/1.1\r\n";
- requestText += "Host: ";
- requestText += url.host;
- requestText += "\r\nConnection: close\r\n";
- if (args.empty()) {
- requestText += "\r\n";
- } else {
- string requestData;
-
- bool isMultipart = false;
- string bondary;
- srand((unsigned int) time(nullptr));
- for (const Argument& item : args) {
- if (item.isFile) {
- isMultipart = true;
- while (bondary.empty() || item.value.find(bondary) != item.value.npos) {
- bondary += StringTools::generateRandomString(4);
- }
- }
- }
- if (isMultipart) {
- requestText += "Content-Type: multipart/form-data; boundary=";
- requestText += bondary;
- requestText += "\r\n";
- for (const Argument& item : args) {
- requestData += "--";
- requestData += bondary;
- requestData += "\r\nContent-Disposition: form-data; name=\"";
- requestData += item.name;
- requestData += "\"\r\n";
- if (item.isFile) {
- requestData += "Content-Type: ";
- requestData += item.mimeType;
- requestData += "\r\n";
- }
- requestData += "\r\n";
- requestData += item.value;
- requestData += "\r\n\r\n";
- }
- } else {
- requestText += "Content-Type: application/x-www-form-urlencoded\r\n";
- bool firstRun = true;
- for (const Argument& item : args) {
- if (firstRun) {
- firstRun = false;
- } else {
- requestData += '&';
- }
- requestData += StringTools::urlEncode(item.name);
- requestData += '=';
- requestData += StringTools::urlEncode(item.value);
- }
- }
-
- requestText += "Content-Length: ";
- requestText += lexical_cast<string>(requestData.length());
- requestText += "\r\n\r\n";
- requestText += requestData;
- }
- write(socket, buffer(requestText.c_str(), requestText.length()));
-
- char buff[1024];
- boost::system::error_code error;
- while (!error) {
- size_t bytes = read(socket, buffer(buff), error);
- result += string(buff, bytes);
- }
-
- cout << "REQUEST" << endl << requestText << endl << "RESPONSE" << endl << result << endl;
-
- size_t headerEnd = result.find("\r\n\r\n");
- if (headerEnd == result.npos) {
- headerEnd = result.find("\n\n");
- }
- if (headerEnd == result.npos) {
- headerEnd = 0;
- }
- result.erase(0, headerEnd);
-
- return result;
-}
-
-void Http::startServer(unsigned short port, const Http::ServerHandler& handler) {
- stopServer();
- _serverHandler = handler;
- tcp::acceptor* acceptor = new tcp::acceptor(_ioService, tcp::endpoint(tcp::v4(), port));
- _tcpServer = new Server<tcp>(this, acceptor);
- _ioService.run();
-}
-
-void Http::startServer(const std::string& unixSocketPath, const Http::ServerHandler& handler) {
- stopServer();
- _serverHandler = handler;
- stream_protocol::acceptor* acceptor = new stream_protocol::acceptor(_ioService, stream_protocol::endpoint(unixSocketPath));
- _unixSocketServer = new Server<stream_protocol>(this, acceptor);
- _ioService.run();
-}
-
-void Http::stopServer() {
- _ioService.stop();
- if (_tcpServer != nullptr) {
- delete _tcpServer;
- _tcpServer = nullptr;
- }
- if (_unixSocketServer != nullptr) {
- delete _unixSocketServer;
- _unixSocketServer = nullptr;
- }
-}
-
-} \ No newline at end of file
diff --git a/src/tgbot/Http.h b/src/tgbot/Http.h
deleted file mode 100644
index aedcd93..0000000
--- a/src/tgbot/Http.h
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (c) 2015 Oleg Morozenkov
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#ifndef TGBOT_CPP_HTTP_H
-#define TGBOT_CPP_HTTP_H
-
-#include <string>
-
-#include <boost/asio.hpp>
-#include <boost/lexical_cast.hpp>
-
-#include "tgbot/Url.h"
-
-namespace TgBot {
-
-class Http {
-
-public:
- typedef std::function<void (const std::string&)> ServerHandler;
-
- class Argument {
-
- public:
- template<typename T>
- Argument(const std::string& name, const T& value, bool isFile = false, const std::string& mimeType = "") :
- name(name), value(boost::lexical_cast<std::string>(value)), isFile(isFile), mimeType(mimeType)
- {
- }
-
- std::string name;
- std::string value;
- bool isFile = false;
- std::string mimeType;
- };
-
- std::string makeRequest(const Url& url, const std::vector<Argument>& args);
- void startServer(unsigned short port, const ServerHandler& handler);
- void startServer(const std::string& unixSocketPath, const ServerHandler& handler);
- void stopServer();
-
-private:
- template<typename Protocol>
- class Connection {
-
- public:
- Connection(Http* http, boost::asio::basic_stream_socket<Protocol>* socket) : http(http), socket(socket) {
- }
-
- ~Connection() {
- delete socket;
- }
-
- void start() {
- data.reserve(10240);
- socket->async_receive(data, [this]() {
- size_t headerEnd = data.find("\r\n\r\n");
- if (headerEnd == data.npos) {
- headerEnd = data.find("\n\n");
- }
- if (headerEnd == data.npos) {
- headerEnd = 0;
- }
- data.erase(0, headerEnd);
- http->_serverHandler(data);
- });
- }
-
- Http* http;
- boost::asio::basic_stream_socket<Protocol>* socket;
- std::string data;
- };
-
- template<typename Protocol>
- class Server {
-
- public:
- Server(Http* http, boost::asio::basic_socket_acceptor<Protocol>* acceptor) : http(http), acceptor(acceptor) {
- }
-
- ~Server() {
- delete acceptor;
- }
-
- void start() {
- boost::asio::basic_stream_socket<Protocol>* socket = new boost::asio::basic_stream_socket<Protocol>(acceptor->get_io_service());
- std::shared_ptr<Http::Connection<Protocol>> connection(new Connection<Protocol>(http, socket));
- acceptor->async_accept(*connection->socket, [this, connection]() {
- connection->start();
- start();
- });
- }
-
- Http* http;
- boost::asio::basic_socket_acceptor<Protocol>* acceptor;
- };
-
-
- boost::asio::io_service _ioService;
- Server<boost::asio::ip::tcp>* _tcpServer = nullptr;
- Server<boost::asio::local::stream_protocol>* _unixSocketServer = nullptr;
- ServerHandler _serverHandler;
-};
-
-}
-
-#endif //TGBOT_CPP_HTTP_H
diff --git a/src/tgbot/Parser.cpp b/src/tgbot/TgTypeParser.cpp
index 3730607..7abcc0e 100644
--- a/src/tgbot/Parser.cpp
+++ b/src/tgbot/TgTypeParser.cpp
@@ -20,14 +20,19 @@
* SOFTWARE.
*/
-#include "Parser.h"
+#include "TgTypeParser.h"
using namespace std;
using namespace boost::property_tree;
namespace TgBot {
-User::Ptr Parser::parseUser(const ptree& data) const {
+TgTypeParser& TgTypeParser::getInstance() {
+ static TgTypeParser result;
+ return result;
+}
+
+User::Ptr TgTypeParser::parseUser(const ptree& data) const {
User::Ptr result(new User);
result->id = data.get<int32_t>("id");
result->firstName = data.get<string>("first_name");
@@ -36,7 +41,7 @@ User::Ptr Parser::parseUser(const ptree& data) const {
return result;
}
-string Parser::parseUser(const User::Ptr& object) const {
+string TgTypeParser::parseUser(const User::Ptr& object) const {
if (!object) {
return "";
}
@@ -51,14 +56,14 @@ string Parser::parseUser(const User::Ptr& object) const {
return result;
}
-GroupChat::Ptr Parser::parseGroupChat(const ptree& data) const {
+GroupChat::Ptr TgTypeParser::parseGroupChat(const ptree& data) const {
GroupChat::Ptr result(new GroupChat);
result->id = data.get<int32_t>("id");
result->title = data.get<string>("title");
return result;
}
-string Parser::parseGroupChat(const GroupChat::Ptr& object) const {
+string TgTypeParser::parseGroupChat(const GroupChat::Ptr& object) const {
if (!object) {
return "";
}
@@ -71,7 +76,7 @@ string Parser::parseGroupChat(const GroupChat::Ptr& object) const {
return result;
}
-Message::Ptr Parser::parseMessage(const ptree& data) const {
+Message::Ptr TgTypeParser::parseMessage(const ptree& data) const {
Message::Ptr result(new Message);
result->messageId = data.get<int32_t>("message_id");
result->from = parseUser(data.find("from")->second);
@@ -97,7 +102,7 @@ Message::Ptr Parser::parseMessage(const ptree& data) const {
return result;
}
-string Parser::parseMessage(const Message::Ptr& object) const {
+string TgTypeParser::parseMessage(const Message::Ptr& object) const {
if (!object) {
return "";
}
@@ -129,7 +134,7 @@ string Parser::parseMessage(const Message::Ptr& object) const {
return result;
}
-PhotoSize::Ptr Parser::parsePhotoSize(const ptree& data) const {
+PhotoSize::Ptr TgTypeParser::parsePhotoSize(const ptree& data) const {
PhotoSize::Ptr result(new PhotoSize);
result->fileId = data.get<string>("file_id");
result->width = data.get<int32_t>("width");
@@ -138,7 +143,7 @@ PhotoSize::Ptr Parser::parsePhotoSize(const ptree& data) const {
return result;
}
-string Parser::parsePhotoSize(const PhotoSize::Ptr& object) const {
+string TgTypeParser::parsePhotoSize(const PhotoSize::Ptr& object) const {
if (!object) {
return "";
}
@@ -153,7 +158,7 @@ string Parser::parsePhotoSize(const PhotoSize::Ptr& object) const {
return result;
}
-Audio::Ptr Parser::parseAudio(const ptree& data) const {
+Audio::Ptr TgTypeParser::parseAudio(const ptree& data) const {
Audio::Ptr result(new Audio);
result->fileId = data.get<string>("file_id");
result->duration = data.get<int32_t>("duration");
@@ -162,7 +167,7 @@ Audio::Ptr Parser::parseAudio(const ptree& data) const {
return result;
}
-string Parser::parseAudio(const Audio::Ptr& object) const {
+string TgTypeParser::parseAudio(const Audio::Ptr& object) const {
if (!object) {
return "";
}
@@ -177,7 +182,7 @@ string Parser::parseAudio(const Audio::Ptr& object) const {
return result;
}
-Document::Ptr Parser::parseDocument(const ptree& data) const {
+Document::Ptr TgTypeParser::parseDocument(const ptree& data) const {
Document::Ptr result(new Document);
result->fileId = data.get<string>("file_id");
result->thumb = parsePhotoSize(data.find("thumb")->second);
@@ -187,7 +192,7 @@ Document::Ptr Parser::parseDocument(const ptree& data) const {
return result;
}
-string Parser::parseDocument(const Document::Ptr& object) const {
+string TgTypeParser::parseDocument(const Document::Ptr& object) const {
if (!object) {
return "";
}
@@ -203,7 +208,7 @@ string Parser::parseDocument(const Document::Ptr& object) const {
return result;
}
-Sticker::Ptr Parser::parseSticker(const ptree& data) const {
+Sticker::Ptr TgTypeParser::parseSticker(const ptree& data) const {
Sticker::Ptr result(new Sticker);
result->fileId = data.get<string>("file_id");
result->width = data.get<int32_t>("width");
@@ -213,7 +218,7 @@ Sticker::Ptr Parser::parseSticker(const ptree& data) const {
return result;
}
-string Parser::parseSticker(const Sticker::Ptr& object) const {
+string TgTypeParser::parseSticker(const Sticker::Ptr& object) const {
if (!object) {
return "";
}
@@ -229,7 +234,7 @@ string Parser::parseSticker(const Sticker::Ptr& object) const {
return result;
}
-Video::Ptr Parser::parseVideo(const ptree& data) const {
+Video::Ptr TgTypeParser::parseVideo(const ptree& data) const {
Video::Ptr result(new Video);
result->fileId = data.get<string>("file_id");
result->width = data.get<int32_t>("width");
@@ -242,7 +247,7 @@ Video::Ptr Parser::parseVideo(const ptree& data) const {
return result;
}
-string Parser::parseVideo(const Video::Ptr& object) const {
+string TgTypeParser::parseVideo(const Video::Ptr& object) const {
if (!object) {
return "";
}
@@ -261,7 +266,7 @@ string Parser::parseVideo(const Video::Ptr& object) const {
return result;
}
-Contact::Ptr Parser::parseContact(const ptree& data) const {
+Contact::Ptr TgTypeParser::parseContact(const ptree& data) const {
Contact::Ptr result(new Contact);
result->phoneNumber = data.get<string>("phone_number");
result->firstName = data.get<string>("first_name");
@@ -270,7 +275,7 @@ Contact::Ptr Parser::parseContact(const ptree& data) const {
return result;
}
-string Parser::parseContact(const Contact::Ptr& object) const {
+string TgTypeParser::parseContact(const Contact::Ptr& object) const {
if (!object) {
return "";
}
@@ -285,14 +290,14 @@ string Parser::parseContact(const Contact::Ptr& object) const {
return result;
}
-Location::Ptr Parser::parseLocation(const ptree& data) const {
+Location::Ptr TgTypeParser::parseLocation(const ptree& data) const {
Location::Ptr result(new Location);
result->longitude = data.get<float>("longitude");
result->latitude = data.get<float>("latitude");
return result;
}
-string Parser::parseLocation(const Location::Ptr& object) const {
+string TgTypeParser::parseLocation(const Location::Ptr& object) const {
if (!object) {
return "";
}
@@ -305,14 +310,14 @@ string Parser::parseLocation(const Location::Ptr& object) const {
return result;
}
-Update::Ptr Parser::parseUpdate(const ptree& data) const {
+Update::Ptr TgTypeParser::parseUpdate(const ptree& data) const {
Update::Ptr result(new Update);
result->updateId = data.get<int32_t>("update_id");
result->message = parseMessage(data.find("message")->second);
return result;
}
-string Parser::parseUpdate(const Update::Ptr& object) const {
+string TgTypeParser::parseUpdate(const Update::Ptr& object) const {
if (!object) {
return "";
}
@@ -325,14 +330,14 @@ string Parser::parseUpdate(const Update::Ptr& object) const {
return result;
}
-UserProfilePhotos::Ptr Parser::parseUserProfilePhotos(const ptree& data) const {
+UserProfilePhotos::Ptr TgTypeParser::parseUserProfilePhotos(const ptree& data) const {
UserProfilePhotos::Ptr result(new UserProfilePhotos);
result->totalCount = data.get<int32_t>("total_count");
result->photos = parse2DArray<PhotoSize>(parsePhotoSize, data, "photos");
return result;
}
-string Parser::parseUserProfilePhotos(const UserProfilePhotos::Ptr& object) const {
+string TgTypeParser::parseUserProfilePhotos(const UserProfilePhotos::Ptr& object) const {
if (!object) {
return "";
}
@@ -345,7 +350,7 @@ string Parser::parseUserProfilePhotos(const UserProfilePhotos::Ptr& object) cons
return result;
}
-ReplyKeyboardMarkup::Ptr Parser::parseReplyKeyboardMarkup(const boost::property_tree::ptree& data) const {
+ReplyKeyboardMarkup::Ptr TgTypeParser::parseReplyKeyboardMarkup(const boost::property_tree::ptree& data) const {
ReplyKeyboardMarkup::Ptr result(new ReplyKeyboardMarkup);
for (const pair<const string, ptree>& item : data.find("keyboard")->second) {
vector<string> array;
@@ -360,7 +365,7 @@ ReplyKeyboardMarkup::Ptr Parser::parseReplyKeyboardMarkup(const boost::property_
return result;
}
-std::string Parser::parseReplyKeyboardMarkup(const ReplyKeyboardMarkup::Ptr& object) const {
+std::string TgTypeParser::parseReplyKeyboardMarkup(const ReplyKeyboardMarkup::Ptr& object) const {
if (!object) {
return "";
}
@@ -387,13 +392,13 @@ std::string Parser::parseReplyKeyboardMarkup(const ReplyKeyboardMarkup::Ptr& obj
return result;
}
-ReplyKeyboardHide::Ptr Parser::parseReplyKeyboardHide(const boost::property_tree::ptree& data) const {
+ReplyKeyboardHide::Ptr TgTypeParser::parseReplyKeyboardHide(const boost::property_tree::ptree& data) const {
ReplyKeyboardHide::Ptr result(new ReplyKeyboardHide);
result->selective = data.get<bool>("selective");
return result;
}
-std::string Parser::parseReplyKeyboardHide(const ReplyKeyboardHide::Ptr& object) const {
+std::string TgTypeParser::parseReplyKeyboardHide(const ReplyKeyboardHide::Ptr& object) const {
if (!object) {
return "";
}
@@ -405,13 +410,13 @@ std::string Parser::parseReplyKeyboardHide(const ReplyKeyboardHide::Ptr& object)
return result;
}
-ForceReply::Ptr Parser::parseForceReply(const boost::property_tree::ptree& data) const {
+ForceReply::Ptr TgTypeParser::parseForceReply(const boost::property_tree::ptree& data) const {
ForceReply::Ptr result(new ForceReply);
result->selective = data.get<bool>("selective");
return result;
}
-std::string Parser::parseForceReply(const ForceReply::Ptr& object) const {
+std::string TgTypeParser::parseForceReply(const ForceReply::Ptr& object) const {
if (!object) {
return "";
}
@@ -423,7 +428,7 @@ std::string Parser::parseForceReply(const ForceReply::Ptr& object) const {
return result;
}
-GenericChat::Ptr Parser::parseGenericChat(const ptree& data) const {
+GenericChat::Ptr TgTypeParser::parseGenericChat(const ptree& data) const {
if (data.find("first_name") == data.not_found()) {
return static_pointer_cast<GenericChat>(parseGroupChat(data));
} else {
@@ -431,7 +436,7 @@ GenericChat::Ptr Parser::parseGenericChat(const ptree& data) const {
}
}
-string Parser::parseGenericChat(const GenericChat::Ptr& object) const {
+string TgTypeParser::parseGenericChat(const GenericChat::Ptr& object) const {
if (!object) {
return "";
}
@@ -442,7 +447,7 @@ string Parser::parseGenericChat(const GenericChat::Ptr& object) const {
}
}
-GenericReply::Ptr Parser::parseGenericReply(const boost::property_tree::ptree& data) const {
+GenericReply::Ptr TgTypeParser::parseGenericReply(const boost::property_tree::ptree& data) const {
if (data.find("force_reply") != data.not_found()) {
return static_pointer_cast<GenericReply>(parseForceReply(data));
} else if (data.find("hide_keyboard") != data.not_found()) {
@@ -452,7 +457,7 @@ GenericReply::Ptr Parser::parseGenericReply(const boost::property_tree::ptree& d
}
}
-std::string Parser::parseGenericReply(const GenericReply::Ptr& object) const {
+std::string TgTypeParser::parseGenericReply(const GenericReply::Ptr& object) const {
if (!object) {
return "";
}
@@ -465,7 +470,7 @@ std::string Parser::parseGenericReply(const GenericReply::Ptr& object) const {
}
}
-void Parser::appendToJson(string& json, const string& varName, const string& value) const {
+void TgTypeParser::appendToJson(string& json, const string& varName, const string& value) const {
if (value.empty()) {
return;
}
diff --git a/src/tgbot/Parser.h b/src/tgbot/TgTypeParser.h
index 48a7cea..0571c25 100644
--- a/src/tgbot/Parser.h
+++ b/src/tgbot/TgTypeParser.h
@@ -20,8 +20,8 @@
* SOFTWARE.
*/
-#ifndef TGBOT_CPP_PARSER_H
-#define TGBOT_CPP_PARSER_H
+#ifndef TGBOT_CPP_TGTYPEPARSER_H
+#define TGBOT_CPP_TGTYPEPARSER_H
#include <string>
@@ -47,9 +47,11 @@
namespace TgBot {
-class Parser {
+class TgTypeParser {
public:
+ static TgTypeParser& getInstance();
+
User::Ptr parseUser(const boost::property_tree::ptree& data) const;
std::string parseUser(const User::Ptr& object) const;
GroupChat::Ptr parseGroupChat(const boost::property_tree::ptree& data) const;
@@ -93,7 +95,7 @@ public:
}
template<typename T>
- std::shared_ptr<T> tryParse(std::shared_ptr<T> (Parser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const {
+ std::shared_ptr<T> tryParse(std::shared_ptr<T> (TgTypeParser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const {
auto treeItem = data.find(keyName);
if (treeItem == data.not_found()) {
return std::shared_ptr<T>();
@@ -102,7 +104,7 @@ public:
}
template<typename T>
- std::vector<std::shared_ptr<T>> parseArray(std::shared_ptr<T> (Parser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const {
+ std::vector<std::shared_ptr<T>> parseArray(std::shared_ptr<T> (TgTypeParser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const {
std::vector<std::shared_ptr<T>> result;
auto treeItem = data.find(keyName);
if (treeItem == data.not_found()) {
@@ -115,7 +117,7 @@ public:
}
template<typename T>
- std::vector<std::vector<std::shared_ptr<T>>> parse2DArray(std::shared_ptr<T> (Parser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const {
+ std::vector<std::vector<std::shared_ptr<T>>> parse2DArray(std::shared_ptr<T> (TgTypeParser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const {
std::vector<std::vector<std::shared_ptr<T>>> result;
auto treeItem = data.find(keyName);
if (treeItem == data.not_found()) {
@@ -132,7 +134,7 @@ public:
}
template<typename T>
- std::string parseArray(std::string (Parser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::shared_ptr<T>>& objects) const {
+ std::string parseArray(std::string (TgTypeParser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::shared_ptr<T>>& objects) const {
std::string result;
result += '[';
for (const std::shared_ptr<T>& item : objects) {
@@ -145,7 +147,7 @@ public:
}
template<typename T>
- std::string parse2DArray(std::string (Parser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::vector<std::shared_ptr<T>>>& objects) const {
+ std::string parse2DArray(std::string (TgTypeParser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::vector<std::shared_ptr<T>>>& objects) const {
std::string result;
result += '[';
for (const std::vector<std::shared_ptr<T>>& item : objects) {
@@ -175,4 +177,4 @@ private:
}
-#endif //TGBOT_CPP_PARSER_H
+#endif //TGBOT_CPP_TGTYPEPARSER_H
diff --git a/src/tgbot/net/HttpClient.cpp b/src/tgbot/net/HttpClient.cpp
new file mode 100644
index 0000000..fefeac5
--- /dev/null
+++ b/src/tgbot/net/HttpClient.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "HttpClient.h"
+
+#include <boost/asio/ssl.hpp>
+
+using namespace std;
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace boost::asio::local;
+
+namespace TgBot {
+
+HttpClient& HttpClient::getInstance() {
+ static HttpClient result;
+ return result;
+}
+
+string HttpClient::makeRequest(const Url& url, const vector<HttpReqArg>& args) {
+ ssl::context context(ssl::context::sslv23);
+ context.set_default_verify_paths();
+
+ ssl::stream<tcp::socket> socket(_ioService, context);
+ tcp::resolver resolver(_ioService);
+ tcp::resolver::query query(url.host, url.protocol);
+
+ connect(socket.lowest_layer(), resolver.resolve(query));
+
+// boost::asio::socket_base::keep_alive keepAliveOption(true);
+// socket.lowest_layer().set_option(keepAliveOption);
+
+ socket.set_verify_mode(ssl::verify_none);
+ socket.set_verify_callback(ssl::rfc2818_verification(url.host));
+ socket.handshake(ssl::stream<tcp::socket>::client);
+
+ string requestText = HttpParser::getInstance().generateRequest(url, args, false);
+ write(socket, buffer(requestText.c_str(), requestText.length()));
+
+ string response;
+ char buff[1024];
+ boost::system::error_code error;
+ while (!error) {
+ size_t bytes = read(socket, buffer(buff), error);
+ response += string(buff, bytes);
+ printf("%s", string(buff, bytes).c_str());
+ }
+
+ return HttpParser::getInstance().parseResponse(response);
+}
+
+}
diff --git a/src/tgbot/net/HttpClient.h b/src/tgbot/net/HttpClient.h
new file mode 100644
index 0000000..4b84dc3
--- /dev/null
+++ b/src/tgbot/net/HttpClient.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_HTTPCLIENT_H
+#define TGBOT_HTTPCLIENT_H
+
+#include <string>
+
+#include <boost/asio.hpp>
+
+#include "tgbot/net/Url.h"
+#include "tgbot/net/HttpReqArg.h"
+#include "tgbot/net/HttpParser.h"
+
+namespace TgBot {
+
+class HttpClient {
+
+public:
+ static HttpClient& getInstance();
+
+ std::string makeRequest(const Url& url, const std::vector<HttpReqArg>& args);
+
+private:
+ boost::asio::io_service _ioService;
+};
+
+}
+
+#endif //TGBOT_HTTPCLIENT_H
diff --git a/src/tgbot/net/HttpParser.cpp b/src/tgbot/net/HttpParser.cpp
new file mode 100644
index 0000000..070c630
--- /dev/null
+++ b/src/tgbot/net/HttpParser.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "HttpParser.h"
+
+#include <boost/algorithm/string.hpp>
+
+#include "tgbot/tools/StringTools.h"
+
+using namespace std;
+using namespace boost;
+
+namespace TgBot {
+
+HttpParser& HttpParser::getInstance() {
+ static HttpParser result;
+ return result;
+}
+
+string HttpParser::generateRequest(const Url& url, const vector<HttpReqArg>& args, bool isKeepAlive) {
+ string result;
+ if (args.empty()) {
+ result += "GET ";
+ } else {
+ result += "POST ";
+ }
+ result += url.path;
+ result += url.query.empty() ? "" : "?" + url.query;
+ result += " HTTP/1.1\r\n";
+ result += "Host: ";
+ result += url.host;
+ result += "\r\nConnection: ";
+ if (isKeepAlive) {
+ result += "keep-alive";
+ } else {
+ result += "close";
+ }
+ result += "\r\n";
+ if (args.empty()) {
+ result += "\r\n";
+ } else {
+ string requestData;
+
+ string bondary = generateMultipartBoundary(args);
+ if (bondary.empty()) {
+ result += "Content-Type: application/x-www-form-urlencoded\r\n";
+ requestData = generateWwwFormUrlencoded(args);
+ } else {
+ result += "Content-Type: multipart/form-data; boundary=";
+ result += bondary;
+ result += "\r\n";
+ requestData = generateMultipartFormData(args, bondary);
+ }
+
+ result += "Content-Length: ";
+ result += lexical_cast<string>(requestData.length());
+ result += "\r\n\r\n";
+ result += requestData;
+ }
+ return result;
+}
+
+string HttpParser::generateMultipartFormData(const vector<HttpReqArg>& args, const string& bondary) {
+ string result;
+ for (const HttpReqArg& item : args) {
+ result += "--";
+ result += bondary;
+ result += "\r\nContent-Disposition: form-data; name=\"";
+ result += item.name;
+ result += "\"\r\n";
+ if (item.isFile) {
+ result += "Content-Type: ";
+ result += item.mimeType;
+ result += "\r\n";
+ }
+ result += "\r\n";
+ result += item.value;
+ result += "\r\n";
+ }
+ return result;
+}
+
+string HttpParser::generateMultipartBoundary(const vector<HttpReqArg>& args) {
+ string result;
+ srand((unsigned int) time(nullptr));
+ for (const HttpReqArg& item : args) {
+ if (item.isFile) {
+ while (result.empty() || item.value.find(result) != item.value.npos) {
+ result += StringTools::generateRandomString(4);
+ }
+ }
+ }
+ return result;
+}
+
+string HttpParser::generateWwwFormUrlencoded(const vector<HttpReqArg>& args) {
+ string result;
+
+ bool firstRun = true;
+ for (const HttpReqArg& item : args) {
+ if (firstRun) {
+ firstRun = false;
+ } else {
+ result += '&';
+ }
+ result += StringTools::urlEncode(item.name);
+ result += '=';
+ result += StringTools::urlEncode(item.value);
+ }
+
+ return result;
+}
+
+string HttpParser::generateResponse(const string& data, const string& mimeType, unsigned short statusCode, const string& statusStr, bool isKeepAlive) {
+ string result;
+ result += "HTTP/1.1 ";
+ result += lexical_cast<string>(statusCode);
+ result += ' ';
+ result += statusStr;
+ result += "\r\nContent-Type: ";
+ result += mimeType;
+ result += "\r\nContent-Length: ";
+ result += lexical_cast<string>(data.length());
+ result += "\r\n\r\n";
+ result += data;
+ return result;
+}
+
+string HttpParser::parseHttp(bool isRequest, const string& data, map<string, string>& headers) {
+ bool onlyNewLineChar = false;
+ size_t headerEnd = data.find("\r\n\r\n");
+ if (headerEnd == data.npos) {
+ headerEnd = data.find("\n\n");
+ if (headerEnd != data.npos) {
+ onlyNewLineChar = true;
+ headerEnd += 2;
+ }
+ } else {
+ headerEnd += 4;
+ }
+
+ size_t lineStart = 0;
+ size_t lineEnd = 0;
+ size_t lineSepPos = 0;
+ size_t lastLineEnd = data.npos;
+ while (lastLineEnd != lineEnd) {
+ lastLineEnd = lineEnd;
+ if (lineEnd == 0) {
+ if (isRequest) {
+ lineSepPos = data.find(' ');
+ lineEnd = data.find(onlyNewLineChar ? "\n" : "\r\n");
+ headers["method"] = data.substr(0, lineSepPos);
+ headers["path"] = data.substr(lineSepPos + 1, data.find(' ', lineSepPos + 1) - lineSepPos - 1);
+ } else {
+ lineSepPos = data.find(' ');
+ lineEnd = data.find(onlyNewLineChar ? "\n" : "\r\n");
+ headers["status"] = data.substr(lineSepPos + 1, data.find(' ', lineSepPos + 1) - lineSepPos - 1);
+ }
+ } else {
+ lineStart = lineEnd;
+ lineStart += onlyNewLineChar ? 1 : 2;
+ lineEnd = data.find(onlyNewLineChar ? "\n" : "\r\n", lineStart);
+ lineSepPos = data.find(':', lineStart);
+ if (lineEnd >= headerEnd || lastLineEnd == lineEnd || lineSepPos >= headerEnd) {
+ break;
+ }
+ headers[to_lower_copy(data.substr(lineStart, lineSepPos - lineStart))] = trim_copy(data.substr(lineSepPos + 1, lineEnd - lineSepPos - 1));
+ }
+ }
+
+ return headerEnd == data.npos ? "" : data.substr(headerEnd);
+}
+
+string HttpParser::parseHttp(bool isRequest, const string& data) {
+ size_t headerEnd = data.find("\r\n\r\n");
+ if (headerEnd == data.npos) {
+ headerEnd = data.find("\n\n");
+ }
+ if (headerEnd == data.npos) {
+ headerEnd = 0;
+ }
+ return data.substr(headerEnd);
+}
+
+}
diff --git a/src/tgbot/net/HttpParser.h b/src/tgbot/net/HttpParser.h
new file mode 100644
index 0000000..dcb857b
--- /dev/null
+++ b/src/tgbot/net/HttpParser.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_HTTPPARSER_H
+#define TGBOT_HTTPPARSER_H
+
+#include <string>
+#include <map>
+
+#include "tgbot/net/Url.h"
+#include "tgbot/net/HttpReqArg.h"
+
+namespace TgBot {
+
+class HttpParser {
+
+public:
+ static HttpParser& getInstance();
+
+ std::string generateRequest(const Url& url, const std::vector<HttpReqArg>& args, bool isKeepAlive = false);
+ std::string generateMultipartFormData(const std::vector<HttpReqArg>& args, const std::string& bondary);
+ std::string generateMultipartBoundary(const std::vector<HttpReqArg>& args);
+ std::string generateWwwFormUrlencoded(const std::vector<HttpReqArg>& args);
+ std::string generateResponse(const std::string& data, const std::string& mimeType = "text/plain", short unsigned statusCode = 200, const std::string& statusStr = "OK", bool isKeepAlive = false);
+
+ inline std::string parseRequest(const std::string& data, std::map<std::string, std::string>& headers) {
+ return parseHttp(true, data, headers);
+ }
+
+ inline std::string parseRequest(const std::string& data) {
+ return parseHttp(true, data);
+ }
+
+ inline std::string parseResponse(const std::string& data, std::map<std::string, std::string>& headers) {
+ return parseHttp(false, data, headers);
+ }
+
+ inline std::string parseResponse(const std::string& data) {
+ return parseHttp(false, data);
+ }
+
+private:
+ std::string parseHttp(bool isRequest, const std::string& data, std::map<std::string, std::string>& headers);
+ std::string parseHttp(bool isRequest, const std::string& data);
+};
+
+}
+
+#endif //TGBOT_HTTPPARSER_H
diff --git a/src/tgbot/net/HttpReqArg.h b/src/tgbot/net/HttpReqArg.h
new file mode 100644
index 0000000..e906e94
--- /dev/null
+++ b/src/tgbot/net/HttpReqArg.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_HTTPPARAMETER_H
+#define TGBOT_HTTPPARAMETER_H
+
+#include <string>
+
+#include <boost/lexical_cast.hpp>
+
+namespace TgBot {
+
+class HttpReqArg {
+
+public:
+ template<typename T>
+ HttpReqArg(const std::string& name, const T& value, bool isFile = false, const std::string& mimeType = "text/plain") :
+ name(name), value(boost::lexical_cast<std::string>(value)), isFile(isFile), mimeType(mimeType)
+ {
+ }
+
+ std::string name;
+ std::string value;
+ bool isFile = false;
+ std::string mimeType;
+};
+
+}
+
+
+#endif //TGBOT_HTTPPARAMETER_H
diff --git a/src/tgbot/net/HttpServer.h b/src/tgbot/net/HttpServer.h
new file mode 100644
index 0000000..92a667a
--- /dev/null
+++ b/src/tgbot/net/HttpServer.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_HTTPSERVER_H
+#define TGBOT_HTTPSERVER_H
+
+#include <string>
+
+#include <boost/asio.hpp>
+
+#include "tgbot/net/HttpParser.h"
+
+namespace TgBot {
+
+template<typename Protocol>
+class HttpServer {
+
+private:
+ class Connection;
+
+public:
+ typedef std::function<std::string (const std::string&, const std::map<std::string, std::string>)> ServerHandler;
+
+ HttpServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const ServerHandler& handler) : _acceptor(acceptor), _handler(handler) {
+ }
+
+ void start() {
+ std::shared_ptr<boost::asio::basic_stream_socket<Protocol>> socket(new boost::asio::basic_stream_socket<Protocol>(acceptor->get_io_service()));
+ std::shared_ptr<Connection<Protocol>> connection(new Connection<Protocol>(socket, _handler));
+ acceptor->async_accept(*connection->socket, [this, connection]() {
+ connection->start();
+ start();
+ });
+ _ioService.run();
+ }
+
+ void stop() {
+ _ioService.stop();
+ }
+
+private:
+ template<typename Protocol>
+ class Connection {
+
+ public:
+ Connection(std::shared_ptr<boost::asio::basic_stream_socket<Protocol>>& socket, const ServerHandler& handler) : socket(socket), _handler(handler) {
+ boost::asio::socket_base::keep_alive option(true);
+ socket.set_option(option);
+ }
+
+ void start() {
+ data.reserve(10240);
+ socket->async_receive(data, [this]() {
+ std::map<std::string, std::string> headers;
+ std::string body = HttpParser::parseResponse(data, headers);
+ socket->async_send(_handler(body, headers));
+ });
+ }
+
+ std::shared_ptr<boost::asio::basic_stream_socket<Protocol>> socket;
+ std::string data;
+
+ private:
+ const ServerHandler _handler;
+ };
+
+ boost::asio::io_service _ioService;
+ std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>> _acceptor;
+ const ServerHandler _handler;
+};
+
+}
+
+#endif //TGBOT_HTTPSERVER_H
diff --git a/src/tgbot/Bot.cpp b/src/tgbot/net/TgLongPoll.cpp
index 091b548..6359ec6 100644
--- a/src/tgbot/Bot.cpp
+++ b/src/tgbot/net/TgLongPoll.cpp
@@ -20,32 +20,23 @@
* SOFTWARE.
*/
-#include "Bot.h"
-
-using namespace std;
-using namespace boost::property_tree;
+#include "TgLongPoll.h"
namespace TgBot {
-Bot::Bot(const string& token) :
- _token(token),
- _api(this),
- _events(this),
- _http(),
- _parser(),
- _webhooksServerHandler([this](const string& data) {
- _events.handleUpdate(_parser.parseUpdate(_parser.parseJson(data)));
- })
-{
+TgLongPoll::TgLongPoll(const Api* api, const EventHandler* eventHandler) : _api(api), _eventHandler(eventHandler) {
+}
+
+TgLongPoll::TgLongPoll(const Bot& bot) : TgLongPoll(&bot.getApi(), &bot.getEventHandler()) {
}
-void Bot::startLongPoll() {
- std::vector<Update::Ptr> updates = _api.getUpdates(_lastUpdateId, 100, 60);
+void TgLongPoll::start() {
+ std::vector<Update::Ptr> updates = _api->getUpdates(_lastUpdateId, 100, 60);
for (Update::Ptr& item : updates) {
if (item->updateId >= _lastUpdateId) {
_lastUpdateId = item->updateId + 1;
}
- _events.handleUpdate(item);
+ _eventHandler->handleUpdate(item);
}
}
diff --git a/src/tgbot/net/TgLongPoll.h b/src/tgbot/net/TgLongPoll.h
new file mode 100644
index 0000000..6c5175e
--- /dev/null
+++ b/src/tgbot/net/TgLongPoll.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_TGLONGPOLL_H
+#define TGBOT_TGLONGPOLL_H
+
+#include "tgbot/Bot.h"
+#include "tgbot/Api.h"
+#include "tgbot/EventHandler.h"
+
+namespace TgBot {
+
+class TgLongPoll {
+
+public:
+ TgLongPoll(const Api* api, const EventHandler* eventHandler);
+ TgLongPoll(const Bot& bot);
+
+ void start();
+
+private:
+ int32_t _lastUpdateId = 0;
+ const Api* _api;
+ const EventHandler* _eventHandler;
+};
+
+}
+
+#endif //TGBOT_TGLONGPOLL_H
diff --git a/src/tgbot/net/TgWebhookLocalServer.h b/src/tgbot/net/TgWebhookLocalServer.h
new file mode 100644
index 0000000..ec99ad7
--- /dev/null
+++ b/src/tgbot/net/TgWebhookLocalServer.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_TGWEBHOOKTCPSERVER_H
+#define TGBOT_TGWEBHOOKTCPSERVER_H
+
+#include "tgbot/net/TgWebhookServer.h"
+
+namespace TgBot {
+
+class TgWebhookLocalServer : public TgWebhookServer<boost::asio::local::stream_protocol> {
+
+public:
+ TgWebhookLocalServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::local::stream_protocol>>& acceptor, const std::string& path, EventHandler* eventHandler) = delete;
+
+ TgWebhookLocalServer(const std::string& path, const EventHandler* eventHandler) :
+ TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::local::stream_protocol>>(new boost::asio::local::stream_protocol::acceptor(_ioService, boost::asio::local::stream_protocol::endpoint(path))), path, eventHandler)
+ {
+ }
+
+ TgWebhookLocalServer(const std::string& path, const Bot& bot) : TgWebhookLocalServer(path, &bot.getEventHandler()) {
+
+ }
+};
+
+}
+
+#endif //TGBOT_TGWEBHOOKTCPSERVER_H
diff --git a/src/tgbot/net/TgWebhookServer.h b/src/tgbot/net/TgWebhookServer.h
new file mode 100644
index 0000000..a8155ff
--- /dev/null
+++ b/src/tgbot/net/TgWebhookServer.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_TGHTTPSERVER_H
+#define TGBOT_TGHTTPSERVER_H
+
+#include "tgbot/Bot.h"
+#include "tgbot/EventHandler.h"
+#include "tgbot/TgTypeParser.h"
+#include "tgbot/net/HttpServer.h"
+
+namespace TgBot {
+
+template<typename Protocol>
+class TgWebhookServer : public HttpServer<Protocol> {
+
+public:
+ TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const ServerHandler& handler) = delete;
+
+ TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const std::string& path, const EventHandler* eventHandler) :
+ HttpServer(acceptor, [this, eventHandler, &path](const std::string& data, const std::map<std::string, std::string>& headers) -> std::string {
+ if (headers["method"] == "POST" && headers["path"] == path) {
+ eventHandler->handleUpdate(TgTypeParser::getInstance().parseUpdate(TgTypeParser::getInstance().parseJson(data)));
+ }
+ return HttpParser::generateResponse("");
+ })
+ {
+ }
+
+ TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const std::string& path, const Bot& bot) :
+ TgWebhookServer(acceptor, path, &bot.getEventHandler())
+ {
+ }
+};
+
+}
+
+#endif //TGBOT_TGHTTPSERVER_H
diff --git a/src/tgbot/net/TgWebhookTcpServer.h b/src/tgbot/net/TgWebhookTcpServer.h
new file mode 100644
index 0000000..4dc58e1
--- /dev/null
+++ b/src/tgbot/net/TgWebhookTcpServer.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_TGWEBHOOKTCPSERVER_H
+#define TGBOT_TGWEBHOOKTCPSERVER_H
+
+#include "tgbot/net/TgWebhookServer.h"
+
+namespace TgBot {
+
+class TgWebhookTcpServer : public TgWebhookServer<boost::asio::ip::tcp> {
+
+public:
+ TgWebhookTcpServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>>& acceptor, const std::string& path, EventHandler* eventHandler) = delete;
+
+ TgWebhookTcpServer(unsigned short port, const std::string& path, const EventHandler* eventHandler) :
+ TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>>(new boost::asio::ip::tcp::acceptor(_ioService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))), path, eventHandler)
+ {
+ }
+
+ TgWebhookTcpServer(const std::string& path, const Bot& bot) : TgWebhookTcpServer(path, &bot.getEventHandler()) {
+ }
+};
+
+}
+
+#endif //TGBOT_TGWEBHOOKTCPSERVER_H
diff --git a/src/tgbot/Url.cpp b/src/tgbot/net/Url.cpp
index 94b9004..7424849 100644
--- a/src/tgbot/Url.cpp
+++ b/src/tgbot/net/Url.cpp
@@ -30,6 +30,7 @@ Url::Url(const string& url) {
bool isProtocolParsed = false;
bool isHostParsed = false;
bool isPathParsed = false;
+ bool isQueryParsed = false;
for (size_t i = 0, count = url.length(); i < count; ++i) {
char c = url[i];
@@ -44,20 +45,32 @@ Url::Url(const string& url) {
} else if (!isHostParsed) {
if (c == '/') {
isHostParsed = true;
- path += c;
+ path += '/';
} else if (c == '?') {
isHostParsed = isPathParsed = true;
+ path += '/';
+ } else if (c == '#') {
+ isHostParsed = isPathParsed = isQueryParsed = true;
+ path += '/';
} else {
host += c;
}
} else if (!isPathParsed) {
if (c == '?') {
isPathParsed = true;
+ } else if (c == '#') {
+ isPathParsed = isQueryParsed = true;
} else {
path += c;
}
+ } else if (!isQueryParsed) {
+ if (c == '#') {
+ isQueryParsed = true;
+ } else {
+ query += c;
+ }
} else {
- query += c;
+ fragment += c;
}
}
}
diff --git a/src/tgbot/Url.h b/src/tgbot/net/Url.h
index de5a7d2..39ae348 100644
--- a/src/tgbot/Url.h
+++ b/src/tgbot/net/Url.h
@@ -36,6 +36,7 @@ public:
std::string host;
std::string path;
std::string query;
+ std::string fragment;
};
}
diff --git a/src/tgbot/tools/StringTools.cpp b/src/tgbot/tools/StringTools.cpp
index 7a8b740..de3bbb8 100644
--- a/src/tgbot/tools/StringTools.cpp
+++ b/src/tgbot/tools/StringTools.cpp
@@ -24,6 +24,7 @@
#include <stdlib.h>
#include <iomanip>
+#include <stdio.h>
using namespace std;
@@ -46,15 +47,15 @@ bool startsWith(const string& str1, const string& str2) {
bool endsWith(const string& str1, const string& str2) {
string::const_iterator it1(str1.end());
- string::const_iterator start1(str1.begin());
- string::const_iterator it2(str2.begin());
- string::const_iterator end2(str2.end());
- while (it1 != start1 && it2 != end2) {
+ string::const_iterator begin1(str1.begin());
+ string::const_iterator it2(str2.end());
+ string::const_iterator begin2(str2.begin());
+ while (it1 != begin1 && it2 != begin2) {
if (*it1 != *it2) {
return false;
}
--it1;
- ++it2;
+ --it2;
}
return true;
}
@@ -78,15 +79,13 @@ string generateRandomString(size_t length) {
}
string urlEncode(const string& value) {
- static const string legitPunctuation = "-_.~!*()'";
+ static const string legitPunctuation = "-_.~";
ostringstream result;
result.fill('0');
result << hex;
for (const char& c : value) {
if (isalnum(c) || legitPunctuation.find(c) != legitPunctuation.npos) {
result << c;
- } else if (c == ' ') {
- result << '+';
} else {
result << '%' << setw(2) << int((unsigned char) c);
}
@@ -95,4 +94,20 @@ string urlEncode(const string& value) {
return result.str();
}
+string urlDecode(const string& value) {
+ string result;
+ for (size_t i = 0, count = value.length(); i < count; ++i) {
+ const char c = value[i];
+ if (c == '%') {
+ int t = 0;
+ sscanf(value.substr(i + 1, 2).c_str(), "%x", &t);
+ result += (char) t;
+ i += 2;
+ } else {
+ result += c;
+ }
+ }
+ return result;
+}
+
}
diff --git a/src/tgbot/tools/StringTools.h b/src/tgbot/tools/StringTools.h
index 0c94338..37ab858 100644
--- a/src/tgbot/tools/StringTools.h
+++ b/src/tgbot/tools/StringTools.h
@@ -34,6 +34,7 @@ bool endsWith(const std::string& str1, const std::string& str2);
void split(const std::string& str, char delimiter, std::vector<std::string>& dest);
std::string generateRandomString(size_t length);
std::string urlEncode(const std::string& value);
+std::string urlDecode(const std::string& value);
inline std::vector<std::string> split(const std::string& str, char delimiter) {
std::vector<std::string> result;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..4139418
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(TEST_SRC_LIST
+ main.cpp
+ utils.cpp
+ tgbot/net/Url.cpp
+ tgbot/net/HttpParser.cpp
+ tgbot/tools/StringTools.cpp
+)
+
+include_directories("${PROJECT_SOURCE_DIR}/test")
+add_executable(${PROJECT_NAME}_test ${TEST_SRC_LIST})
+target_link_libraries(${PROJECT_NAME}_test TgBot)
+add_test(${PROJECT_NAME}_test ${PROJECT_NAME}_test) \ No newline at end of file
diff --git a/test/main.cpp b/test/main.cpp
new file mode 100644
index 0000000..e4b12bf
--- /dev/null
+++ b/test/main.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE TgBot
+
+#include <boost/test/unit_test.hpp>
diff --git a/test/tgbot/net/HttpParser.cpp b/test/tgbot/net/HttpParser.cpp
new file mode 100644
index 0000000..57ba187
--- /dev/null
+++ b/test/tgbot/net/HttpParser.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <boost/test/unit_test.hpp>
+
+#include <tgbot/net/HttpParser.h>
+
+#include "utils.h"
+
+using namespace std;
+using namespace TgBot;
+
+BOOST_AUTO_TEST_SUITE(tHttpParser)
+
+BOOST_AUTO_TEST_CASE(generateRequest) {
+ vector<HttpReqArg> args = { HttpReqArg("email", "test@example.com"), HttpReqArg("text", "Hello, world!") };
+ string t = HttpParser::getInstance().generateRequest(Url("http://example.com/index.html"), args, true);
+ string e = ""
+ "POST /index.html HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: 49\r\n"
+ "\r\n"
+ "email=test%40example.com&text=Hello%2c%20world%21";
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+}
+
+BOOST_AUTO_TEST_CASE(generateMultipartFormData) {
+ vector<HttpReqArg> args = { HttpReqArg("email", "test@example.com"), HttpReqArg("text", "Hello, world!", true) };
+ string boundary = HttpParser::getInstance().generateMultipartBoundary(args);
+ string t = HttpParser::getInstance().generateMultipartFormData(args, boundary);
+ string e = ""
+ "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"email\"\r\n"
+ "\r\n"
+ "test@example.com\r\n"
+ "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"text\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "Hello, world!\r\n";
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+}
+
+BOOST_AUTO_TEST_CASE(generateWwwFormUrlencoded) {
+ vector<HttpReqArg> args = { HttpReqArg("email", "test@example.com"), HttpReqArg("text", "Hello, world!") };
+ string t = HttpParser::getInstance().generateWwwFormUrlencoded(args);
+ string e = "email=test%40example.com&text=Hello%2c%20world%21";
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+}
+
+BOOST_AUTO_TEST_CASE(generateResponse) {
+ string t = HttpParser::getInstance().generateResponse("testdata");
+ string e = ""
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 8\r\n"
+ "\r\n"
+ "testdata";
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+}
+
+BOOST_AUTO_TEST_CASE(parseRequest) {
+ string data = ""
+ "POST /index.html HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 8\r\n"
+ "\r\n"
+ "testdata";
+
+ map<string, string> tHeaders;
+ string t = HttpParser::getInstance().parseRequest(data, tHeaders);
+
+ map<string, string> eHeaders = {
+ { "method", "POST" },
+ { "path", "/index.html" },
+ { "host", "example.com" },
+ { "connection", "keep-alive" },
+ { "content-type", "text/plain" },
+ { "content-length", "8" }
+ };
+ string e = "testdata";
+
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+ BOOST_CHECK_MESSAGE(tHeaders == eHeaders, diff(tHeaders, eHeaders, [](const pair<const string, string>& item) -> string {
+ return item.first + '=' + item.second;
+ }));
+}
+
+BOOST_AUTO_TEST_CASE(parseResponse) {
+ string data = ""
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 8\r\n"
+ "\r\n"
+ "testdata";
+
+ map<string, string> tHeaders;
+ string t = HttpParser::getInstance().parseResponse(data, tHeaders);
+
+ map<string, string> eHeaders = {
+ { "status", "200" },
+ { "content-type", "text/plain" },
+ { "content-length", "8" }
+ };
+ string e = "testdata";
+
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+ BOOST_CHECK_MESSAGE(tHeaders == eHeaders, diff(tHeaders, eHeaders, [](const pair<const string, string>& item) -> string {
+ return item.first + '=' + item.second;
+ }));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/tgbot/net/Url.cpp b/test/tgbot/net/Url.cpp
new file mode 100644
index 0000000..0974ab6
--- /dev/null
+++ b/test/tgbot/net/Url.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <boost/test/unit_test.hpp>
+
+#include <tgbot/net/Url.h>
+
+using namespace TgBot;
+
+BOOST_AUTO_TEST_SUITE(tUrl)
+
+BOOST_AUTO_TEST_CASE(parsingUrlNoPath) {
+ Url t("https://test.example.com?test=123&123=test#title");
+ BOOST_CHECK_EQUAL(t.protocol, "https");
+ BOOST_CHECK_EQUAL(t.host, "test.example.com");
+ BOOST_CHECK_EQUAL(t.path, "/");
+ BOOST_CHECK_EQUAL(t.query, "test=123&123=test");
+ BOOST_CHECK_EQUAL(t.fragment, "title");
+}
+
+BOOST_AUTO_TEST_CASE(parsingUrlNoPathAndQuery) {
+ Url t("https://test.example.com#title");
+ BOOST_CHECK_EQUAL(t.protocol, "https");
+ BOOST_CHECK_EQUAL(t.host, "test.example.com");
+ BOOST_CHECK_EQUAL(t.path, "/");
+ BOOST_CHECK_EQUAL(t.query, "");
+ BOOST_CHECK_EQUAL(t.fragment, "title");
+}
+
+BOOST_AUTO_TEST_CASE(parsingUrlFull) {
+ Url t("https://test.example.com/example-page/index.html?test=123&123=test#title");
+ BOOST_CHECK_EQUAL(t.protocol, "https");
+ BOOST_CHECK_EQUAL(t.host, "test.example.com");
+ BOOST_CHECK_EQUAL(t.path, "/example-page/index.html");
+ BOOST_CHECK_EQUAL(t.query, "test=123&123=test");
+ BOOST_CHECK_EQUAL(t.fragment, "title");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/tgbot/tools/StringTools.cpp b/test/tgbot/tools/StringTools.cpp
new file mode 100644
index 0000000..c3bf709
--- /dev/null
+++ b/test/tgbot/tools/StringTools.cpp
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <string>
+#include <vector>
+
+#include <boost/test/unit_test.hpp>
+
+#include <tgbot/tools/StringTools.h>
+
+#include "utils.h"
+
+using namespace std;
+
+BOOST_AUTO_TEST_SUITE(tStringTools)
+
+BOOST_AUTO_TEST_CASE(startsWith) {
+ BOOST_CHECK(StringTools::startsWith("abc123", "abc"));
+ BOOST_CHECK(StringTools::startsWith("abc", "abc123"));
+}
+
+BOOST_AUTO_TEST_CASE(endsWith) {
+ BOOST_CHECK(StringTools::endsWith("abc123", "123"));
+ BOOST_CHECK(StringTools::endsWith("123", "abc123"));
+}
+
+BOOST_AUTO_TEST_CASE(split) {
+ BOOST_CHECK(StringTools::split("123 456 789", ' ') == vector<string>({"123", "456", "789"}));
+}
+
+BOOST_AUTO_TEST_CASE(urlEncode) {
+ string t = StringTools::urlEncode("`1234567890-qwertyuiop[]\\asdfghjkl;'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:ZXCVBNM<>? ");
+ string e = "%601234567890-qwertyuiop%5b%5d%5casdfghjkl%3b%27zxcvbnm%2c.%2f~%21%40%23%24%25%5e%26%2a%28%29_%2bQWERTYUIOP%7b%7d%7cASDFGHJKL%3aZXCVBNM%3c%3e%3f%20";
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+}
+
+BOOST_AUTO_TEST_CASE(urlDecode) {
+ string t = StringTools::urlDecode("%601234567890-qwertyuiop%5b%5d%5casdfghjkl%3b%27zxcvbnm%2c.%2f~%21%40%23%24%25%5e%26%2a%28%29_%2bQWERTYUIOP%7b%7d%7cASDFGHJKL%3aZXCVBNM%3c%3e%3f%20");
+ string e = "`1234567890-qwertyuiop[]\\asdfghjkl;'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:ZXCVBNM<>? ";
+ BOOST_CHECK_MESSAGE(t == e, diff(t, e));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/utils.cpp b/test/utils.cpp
new file mode 100644
index 0000000..9a9f48c7
--- /dev/null
+++ b/test/utils.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "utils.h"
+
+#include <sstream>
+
+using namespace std;
+using namespace boost;
+
+string diff(const string& test, const string& expected) {
+ string result;
+ result += "\n*** BEGIN ***\n";
+
+ istringstream ss1(test);
+ istringstream ss2(expected);
+
+ string s1, s2;
+ bool r1, r2;
+ size_t i = 0;
+ do {
+ r1 = getline(ss1, s1) ? true : false;
+ r2 = getline(ss2, s2) ? true : false;
+ if (r1 && r2 && s1 == s2) {
+ result += lexical_cast<string>(i);
+ result += " [=] ";
+ result += s1;
+ result += "\n";
+ } else {
+ if (r1) {
+ result += lexical_cast<string>(i);
+ result += " [t] ";
+ result += s1;
+ result += "\n";
+ }
+ if (r2) {
+ result += lexical_cast<string>(i);
+ result += " [e] ";
+ result += s2;
+ result += "\n";
+ }
+ }
+ ++i;
+ } while (r1 || r2);
+
+ result += "*** END ***\n";
+ return result;
+}
diff --git a/test/utils.h b/test/utils.h
new file mode 100644
index 0000000..d8e64e3
--- /dev/null
+++ b/test/utils.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2015 Oleg Morozenkov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TGBOT_UTILS_H
+#define TGBOT_UTILS_H
+
+#include <string>
+
+#include <boost/lexical_cast.hpp>
+
+std::string diff(const std::string& test, const std::string& expected);
+
+template<typename T>
+std::string diff(const T& test, const T& expected, std::string (*toStringFunc)(const typename T::value_type&)) {
+ std::string result;
+ result += "\n*** BEGIN *** Count: t=";
+ result += boost::lexical_cast<std::string>(test.size());
+ result += " e=";
+ result += boost::lexical_cast<std::string>(expected.size());
+ result += '\n';
+
+ typename T::const_iterator iter1 = test.begin();
+ typename T::const_iterator end1 = test.end();
+ typename T::const_iterator iter2 = expected.begin();
+ typename T::const_iterator end2 = expected.end();
+ bool r1, r2;
+ std::string s1, s2;
+ size_t i = 0;
+ do {
+ r1 = iter1 != end1;
+ r2 = iter2 != end2;
+ if (r1) {
+ s1 = toStringFunc(*iter1++);
+ }
+ if (r2) {
+ s2 = toStringFunc(*iter2++);
+ }
+ if (r1 && r2 && s1 == s2) {
+ result += boost::lexical_cast<std::string>(i);
+ result += " [=] ";
+ result += s1;
+ result += "\n";
+ } else {
+ if (r1) {
+ result += boost::lexical_cast<std::string>(i);
+ result += " [t] ";
+ result += s1;
+ result += "\n";
+ }
+ if (r2) {
+ result += boost::lexical_cast<std::string>(i);
+ result += " [e] ";
+ result += s2;
+ result += "\n";
+ }
+ }
+ ++i;
+ } while (r1 || r2);
+
+ result += "*** END ***\n";
+ return result;
+}
+
+#endif //TGBOT_UTILS_H