Рефакторинг.

This commit is contained in:
Kirill Kirilenko 2022-11-09 18:19:28 +03:00
parent 0de99a97e8
commit a4a53a6f19
9 changed files with 199 additions and 130 deletions

View file

@ -1,11 +0,0 @@
#include "Asio.h"
#include <boost/certify/https_verification.hpp>
void initSSL()
{
sslContext.set_verify_mode(boost::asio::ssl::verify_peer |
boost::asio::ssl::verify_fail_if_no_peer_cert);
sslContext.set_default_verify_paths();
boost::certify::enable_native_https_server_verification(sslContext);
}

13
Asio.h
View file

@ -1,13 +0,0 @@
#ifndef COURT_MONITOR_ASIO_H
#define COURT_MONITOR_ASIO_H
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
static boost::asio::io_context asioContext;
static boost::asio::ssl::context sslContext(boost::asio::ssl::context::tls_client);
void initSSL();
#endif // COURT_MONITOR_ASIO_H

104
Bot.cpp Normal file
View file

@ -0,0 +1,104 @@
#include "Bot.h"
#include <banana/api.hpp>
#include <fmt/format.h>
#include <boost/asio/ssl/context.hpp>
namespace {
boost::asio::ssl::context sslContext(boost::asio::ssl::context::tlsv13_client);
}
Bot::Bot(boost::asio::io_context& asioContext, LocalStorage& storage, bool& terminationFlag)
: storage_(storage),
terminationFlag_(terminationFlag),
agent_(storage.token, asioContext, sslContext)
{
getUpdates();
}
void Bot::setupCommands()
{
banana::api::set_my_commands_args_t args;
args.commands.push_back(banana::api::bot_command_t{
"/subscribe_case", "Подписаться на обновления дела по его номеру"});
args.commands.push_back(banana::api::bot_command_t{"/find_case", "Найти дело по параметрам"});
args.scope = banana::api::bot_command_scope_all_private_chats_t{"all_private_chats"};
banana::api::set_my_commands(agent_, std::move(args),
[](banana::expected<banana::boolean_t> result)
{
if (result)
fmt::print("commands set up successfully\n");
else
fmt::print(stderr, "failed to set up commands\n");
});
}
void Bot::notifyUser(int userId,
const std::string& caseNumber,
std::string caseUrl,
const CaseHistoryItem& item)
{
caseUrl = fmt::format("https://mirsud.spb.ru{}", caseUrl);
std::string message = fmt::format(
"Новое событие по делу [№{}]({}):\n"
"{}\n"
"Дата: {} {}\n",
caseNumber, caseUrl, item.status, item.date, item.time);
banana::api::send_message(agent_, {.chat_id = userId, .text = message, .parse_mode = "markdown"},
[](const auto&) {});
}
void Bot::getUpdates()
{
auto handler = [this](banana::expected<banana::array_t<banana::api::update_t>> updates)
{
if (terminationFlag_)
return;
if (updates)
{
for (const auto& update : *updates)
{
processUpdate(update);
updatesOffset_ = update.update_id + 1;
}
getUpdates();
}
else
fmt::print(stderr, "failed to get updates: {}\n", updates.error());
};
banana::api::get_updates(agent_, {.offset = updatesOffset_, .timeout = 50}, std::move(handler));
}
void Bot::processUpdate(const banana::api::update_t& update)
{
if (update.message)
{
if (update.message->text)
{
fmt::print("rx: {}\n", *update.message->text);
if (*update.message->text == "/start")
processStartCommand(*update.message);
else if (*update.message->text == "/subscribe_case")
processSubscribeCaseCommand(*update.message);
else
{
// TODO
}
}
else
fmt::print("skip message without text"); // TODO ответить
}
else
fmt::print("skip unknown update type");
}
void Bot::processStartCommand(const banana::api::message_t& message)
{
}
void Bot::processSubscribeCaseCommand(const banana::api::message_t& message)
{
}

35
Bot.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef COURT_MONITOR_BOT_H
#define COURT_MONITOR_BOT_H
#include "CourtApi.h"
#include "Storage.h"
#include <banana/agent/beast.hpp>
#include <banana/types_fwd.hpp>
class Bot
{
public:
explicit Bot(boost::asio::io_context& asioContext, LocalStorage& storage, bool& terminationFlag);
void setupCommands();
void notifyUser(int userId,
const std::string& caseNumber,
std::string caseUrl,
const CaseHistoryItem& item);
private:
void getUpdates();
void processUpdate(const banana::api::update_t& update);
void processStartCommand(const banana::api::message_t& message);
void processSubscribeCaseCommand(const banana::api::message_t& message);
LocalStorage& storage_;
bool& terminationFlag_;
banana::agent::beast_callback agent_;
std::int64_t updatesOffset_ = 0;
};
#endif // COURT_MONITOR_BOT_H

View file

@ -11,7 +11,7 @@ find_package(Boost COMPONENTS system REQUIRED)
add_subdirectory(external)
add_executable(court_monitor
Asio.cpp
Bot.cpp
CourtApi.cpp
Storage.cpp
main.cpp

View file

@ -1,10 +1,9 @@
#include "CourtApi.h"
#include "Asio.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/beast.hpp>
#include <boost/certify/extensions.hpp>
@ -15,10 +14,17 @@
const char* serverDomain = "mirsud.spb.ru";
namespace {
boost::asio::ssl::context sslContext(boost::asio::ssl::context::tlsv13_client);
using ssl_stream = boost::asio::ssl::stream<boost::beast::tcp_stream>;
} // namespace
ssl_stream connect(const std::string& hostname)
ssl_stream connect(boost::asio::io_context& asioContext, const std::string& hostname)
{
sslContext.set_verify_mode(boost::asio::ssl::verify_peer |
boost::asio::ssl::verify_fail_if_no_peer_cert);
sslContext.set_default_verify_paths();
boost::certify::enable_native_https_server_verification(sslContext);
ssl_stream stream(asioContext, sslContext);
static boost::asio::ip::tcp::resolver resolver(asioContext);
@ -77,9 +83,11 @@ nlohmann::json getResults(ssl_stream& stream, const std::string_view& uuid)
fmt::format("failed to retrieve JSON (server returned code {})", status));
}
nlohmann::json getCaseDetails(int courtId, const std::string_view& caseNumber)
nlohmann::json getCaseDetails(boost::asio::io_context& asioContext,
int courtId,
const std::string_view& caseNumber)
{
ssl_stream stream = connect(serverDomain);
ssl_stream stream = connect(asioContext, serverDomain);
int status;
std::string result;
@ -105,3 +113,20 @@ nlohmann::json getCaseDetails(int courtId, const std::string_view& caseNumber)
throw std::runtime_error(
fmt::format("failed to retrieve JSON (server returned code {})", status));
}
std::vector<CaseHistoryItem> parseHistory(const nlohmann::json& details)
{
std::vector<CaseHistoryItem> items;
const auto& history = details.at("history");
for (const auto& obj : history)
{
CaseHistoryItem item;
item.date = obj.at("date").get<std::string>();
item.time = obj.at("time").get<std::string>();
item.status = obj.at("status").get<std::string>();
item.publishDate = obj.at("publish_date").get<std::string>();
item.publishTime = obj.at("publish_time").get<std::string>();
items.push_back(std::move(item));
}
return items;
}

View file

@ -3,10 +3,27 @@
#include <nlohmann/json_fwd.hpp>
#include <boost/asio/io_context.hpp>
#include <string>
#include <string_view>
#include <vector>
nlohmann::json findCases(const std::string_view& name);
struct CaseHistoryItem
{
std::string date;
std::string time;
std::string status;
std::string publishDate;
std::string publishTime;
};
nlohmann::json getCaseDetails(int courtId, const std::string_view& caseNumber);
nlohmann::json findCases(boost::asio::io_context& asioContext, const std::string_view& name);
nlohmann::json getCaseDetails(boost::asio::io_context& asioContext,
int courtId,
const std::string_view& caseNumber);
std::vector<CaseHistoryItem> parseHistory(const nlohmann::json& details);
#endif // COURT_MONITOR_COURT_API_H

2
external/banana vendored

@ -1 +1 @@
Subproject commit 075ac78bba0950341c9c477ca38a523730857fee
Subproject commit 787d34ea6f616d3a37a09c7262c07d0ff1f0bef4

106
main.cpp
View file

@ -1,12 +1,13 @@
#include "Asio.h"
#include "Bot.h"
#include "CourtApi.h"
#include "Storage.h"
#include <banana/agent/beast.hpp>
#include <banana/api.hpp>
#include <fmt/format.h>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/system_timer.hpp>
#include <chrono>
@ -14,49 +15,9 @@
#include <ctime>
#include <string>
struct CaseHistoryItem
{
std::string date;
std::string time;
std::string status;
std::string publishDate;
std::string publishTime;
};
boost::asio::io_context asioContext;
std::vector<CaseHistoryItem> parseHistory(const nlohmann::json& details)
{
std::vector<CaseHistoryItem> items;
const auto& history = details.at("history");
for (const auto& obj : history)
{
CaseHistoryItem item;
item.date = obj.at("date").get<std::string>();
item.time = obj.at("time").get<std::string>();
item.status = obj.at("status").get<std::string>();
item.publishDate = obj.at("publish_date").get<std::string>();
item.publishTime = obj.at("publish_time").get<std::string>();
items.push_back(std::move(item));
}
return items;
}
void notifyUser(banana::agent::beast_callback& bot,
int userId,
const std::string& caseNumber,
std::string caseUrl,
const CaseHistoryItem& item)
{
caseUrl = fmt::format("https://mirsud.spb.ru{}", caseUrl);
std::string message = fmt::format(
"Новое событие по делу [№{}]({}):\n"
"{}\n"
"Дата: {} {}\n",
caseNumber, caseUrl, item.status, item.date, item.time);
banana::api::send_message(bot, {.chat_id = userId, .text = message, .parse_mode = "markdown"},
[](const auto&) {});
}
void processAllSubscriptions(LocalStorage& storage, banana::agent::beast_callback& bot)
void processAllSubscriptions(LocalStorage& storage, Bot& bot)
{
for (auto& subscription : storage.subscriptions)
{
@ -66,13 +27,13 @@ void processAllSubscriptions(LocalStorage& storage, banana::agent::beast_callbac
for (auto& counter : subscription.counters)
{
fmt::print("** Processing case {}\n", counter.caseNumber);
auto details = getCaseDetails(counter.courtId, counter.caseNumber);
auto details = getCaseDetails(asioContext, counter.courtId, counter.caseNumber);
fmt::print("{}\n", details.dump());
auto url = details["url"].get<std::string>();
auto history = parseHistory(details);
for (std::size_t i = counter.value; i < history.size(); i++)
notifyUser(bot, subscription.userId, counter.caseNumber, url, history[i]);
bot.notifyUser(subscription.userId, counter.caseNumber, url, history[i]);
counter.value = history.size();
}
}
@ -84,17 +45,6 @@ void processAllSubscriptions(LocalStorage& storage, banana::agent::beast_callbac
}
}
void processUpdate(const banana::api::update_t& update)
{
if (update.message)
{
if (update.message->text)
{
fmt::print("rx: {}\n", *update.message->text);
}
}
}
std::chrono::system_clock::time_point getNextCheckTime(std::uint32_t checkTime)
{
std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
@ -116,39 +66,6 @@ void handleSignal(int)
asioContext.stop();
}
int64_t offset = 0;
void getUpdates(banana::agent::beast_callback& bot);
void processUpdates(banana::agent::beast_callback bot,
banana::expected<banana::array_t<banana::api::update_t>> updates)
{
if (terminate)
{
fmt::print("exit\n");
return;
}
if (updates)
{
for (const auto& update : *updates)
{
processUpdate(update);
offset = update.update_id + 1;
}
}
else
fmt::print(stderr, "failed to get updates: {}\n", updates.error());
getUpdates(bot);
}
void getUpdates(banana::agent::beast_callback& bot)
{
banana::api::get_updates(bot, {.offset = offset, .timeout = 50},
[bot](auto updates) { processUpdates(bot, std::move(updates)); });
}
int main()
{
std::signal(SIGTERM, handleSignal);
@ -160,11 +77,9 @@ int main()
loadStorage(storage);
fmt::print("Storage loaded\n");
// Инициализировать SSL
initSSL();
// Создать бота
banana::agent::beast_callback bot(storage.token, asioContext, sslContext);
Bot bot(asioContext, storage, terminate);
bot.setupCommands();
// Создать таймер ежедневной проверки
boost::asio::system_timer checkTimer(asioContext);
@ -181,9 +96,6 @@ int main()
checkTimer.expires_at(getNextCheckTime(storage.checkTime));
checkTimer.async_wait(onTimer);
// Запустить асинхронное получение обновлений
getUpdates(bot);
// Запустить цикл обработки событий
asioContext.run();