mirror of
https://github.com/UltraCoderRU/court_monitor.git
synced 2026-01-28 02:15:12 +00:00
Рефакторинг.
This commit is contained in:
parent
0de99a97e8
commit
a4a53a6f19
9 changed files with 199 additions and 130 deletions
11
Asio.cpp
11
Asio.cpp
|
|
@ -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
13
Asio.h
|
|
@ -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
104
Bot.cpp
Normal 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
35
Bot.h
Normal 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
|
||||||
|
|
@ -11,7 +11,7 @@ find_package(Boost COMPONENTS system REQUIRED)
|
||||||
add_subdirectory(external)
|
add_subdirectory(external)
|
||||||
|
|
||||||
add_executable(court_monitor
|
add_executable(court_monitor
|
||||||
Asio.cpp
|
Bot.cpp
|
||||||
CourtApi.cpp
|
CourtApi.cpp
|
||||||
Storage.cpp
|
Storage.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
|
|
|
||||||
35
CourtApi.cpp
35
CourtApi.cpp
|
|
@ -1,10 +1,9 @@
|
||||||
#include "CourtApi.h"
|
#include "CourtApi.h"
|
||||||
|
|
||||||
#include "Asio.h"
|
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio/ssl/context.hpp>
|
||||||
#include <boost/asio/ssl/stream.hpp>
|
#include <boost/asio/ssl/stream.hpp>
|
||||||
#include <boost/beast.hpp>
|
#include <boost/beast.hpp>
|
||||||
#include <boost/certify/extensions.hpp>
|
#include <boost/certify/extensions.hpp>
|
||||||
|
|
@ -15,10 +14,17 @@
|
||||||
|
|
||||||
const char* serverDomain = "mirsud.spb.ru";
|
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>;
|
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);
|
ssl_stream stream(asioContext, sslContext);
|
||||||
|
|
||||||
static boost::asio::ip::tcp::resolver resolver(asioContext);
|
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));
|
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;
|
int status;
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|
@ -105,3 +113,20 @@ nlohmann::json getCaseDetails(int courtId, const std::string_view& caseNumber)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
fmt::format("failed to retrieve JSON (server returned code {})", status));
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
21
CourtApi.h
21
CourtApi.h
|
|
@ -3,10 +3,27 @@
|
||||||
|
|
||||||
#include <nlohmann/json_fwd.hpp>
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio/io_context.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#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
|
#endif // COURT_MONITOR_COURT_API_H
|
||||||
|
|
|
||||||
2
external/banana
vendored
2
external/banana
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 075ac78bba0950341c9c477ca38a523730857fee
|
Subproject commit 787d34ea6f616d3a37a09c7262c07d0ff1f0bef4
|
||||||
106
main.cpp
106
main.cpp
|
|
@ -1,12 +1,13 @@
|
||||||
#include "Asio.h"
|
#include "Bot.h"
|
||||||
#include "CourtApi.h"
|
#include "CourtApi.h"
|
||||||
#include "Storage.h"
|
#include "Storage.h"
|
||||||
|
|
||||||
#include <banana/agent/beast.hpp>
|
#include <banana/agent/beast.hpp>
|
||||||
#include <banana/api.hpp>
|
#include <banana/api.hpp>
|
||||||
#include <fmt/format.h>
|
#include <fmt/core.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <boost/asio/system_timer.hpp>
|
#include <boost/asio/system_timer.hpp>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
@ -14,49 +15,9 @@
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct CaseHistoryItem
|
boost::asio::io_context asioContext;
|
||||||
{
|
|
||||||
std::string date;
|
|
||||||
std::string time;
|
|
||||||
std::string status;
|
|
||||||
std::string publishDate;
|
|
||||||
std::string publishTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<CaseHistoryItem> parseHistory(const nlohmann::json& details)
|
void processAllSubscriptions(LocalStorage& storage, Bot& bot)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
for (auto& subscription : storage.subscriptions)
|
for (auto& subscription : storage.subscriptions)
|
||||||
{
|
{
|
||||||
|
|
@ -66,13 +27,13 @@ void processAllSubscriptions(LocalStorage& storage, banana::agent::beast_callbac
|
||||||
for (auto& counter : subscription.counters)
|
for (auto& counter : subscription.counters)
|
||||||
{
|
{
|
||||||
fmt::print("** Processing case {}\n", counter.caseNumber);
|
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());
|
fmt::print("{}\n", details.dump());
|
||||||
auto url = details["url"].get<std::string>();
|
auto url = details["url"].get<std::string>();
|
||||||
|
|
||||||
auto history = parseHistory(details);
|
auto history = parseHistory(details);
|
||||||
for (std::size_t i = counter.value; i < history.size(); i++)
|
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();
|
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::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());
|
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();
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
std::signal(SIGTERM, handleSignal);
|
std::signal(SIGTERM, handleSignal);
|
||||||
|
|
@ -160,11 +77,9 @@ int main()
|
||||||
loadStorage(storage);
|
loadStorage(storage);
|
||||||
fmt::print("Storage loaded\n");
|
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);
|
boost::asio::system_timer checkTimer(asioContext);
|
||||||
|
|
@ -181,9 +96,6 @@ int main()
|
||||||
checkTimer.expires_at(getNextCheckTime(storage.checkTime));
|
checkTimer.expires_at(getNextCheckTime(storage.checkTime));
|
||||||
checkTimer.async_wait(onTimer);
|
checkTimer.async_wait(onTimer);
|
||||||
|
|
||||||
// Запустить асинхронное получение обновлений
|
|
||||||
getUpdates(bot);
|
|
||||||
|
|
||||||
// Запустить цикл обработки событий
|
// Запустить цикл обработки событий
|
||||||
asioContext.run();
|
asioContext.run();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue