Наработки по машинам состояния.

This commit is contained in:
Kirill Kirilenko 2022-11-19 21:42:18 +03:00
parent 4309860ddb
commit 193dabe805
13 changed files with 293 additions and 108 deletions

35
Bot.cpp
View file

@ -94,31 +94,30 @@ void Bot::processUpdate(const banana::api::update_t& update)
else else
LOG(bot, "incoming message: user={} (not text)", userId); LOG(bot, "incoming message: user={} (not text)", userId);
auto sessionIt = sessions_.find(userId); auto& session = getOrCreateSession(userId);
if (sessionIt == sessions_.end()) session.processMessage(*update.message);
{
bool ok;
std::tie(sessionIt, ok) =
sessions_.emplace(std::piecewise_construct, std::forward_as_tuple(userId),
std::forward_as_tuple(agent_, userId));
}
sessionIt->second.processMessage(*update.message);
} }
else if (update.callback_query) else if (update.callback_query)
{ {
banana::integer_t userId = update.callback_query->from.id; banana::integer_t userId = update.callback_query->from.id;
LOG(bot, "incoming callback query: user={} data='{}'", userId, *update.callback_query->data); LOG(bot, "incoming callback query: user={} data='{}'", userId, *update.callback_query->data);
auto sessionIt = sessions_.find(userId); auto& session = getOrCreateSession(userId);
if (sessionIt == sessions_.end()) session.processCallbackQuery(*update.callback_query);
{
bool ok;
std::tie(sessionIt, ok) =
sessions_.emplace(std::piecewise_construct, std::forward_as_tuple(userId),
std::forward_as_tuple(agent_, userId));
}
sessionIt->second.processCallbackQuery(*update.callback_query);
} }
else else
LOG(bot, "skip unknown update type"); LOG(bot, "skip unknown update type");
} }
BotSession& Bot::getOrCreateSession(banana::integer_t userId)
{
auto sessionIt = sessions_.find(userId);
if (sessionIt == sessions_.end())
{
bool ok;
std::tie(sessionIt, ok) =
sessions_.emplace(std::piecewise_construct, std::forward_as_tuple(userId),
std::forward_as_tuple(agent_, userId, storage_));
}
return sessionIt->second;
}

1
Bot.h
View file

@ -25,6 +25,7 @@ private:
void getUpdates(); void getUpdates();
void processUpdate(const banana::api::update_t& update); void processUpdate(const banana::api::update_t& update);
BotSession& getOrCreateSession(banana::integer_t userId);
LocalStorage& storage_; LocalStorage& storage_;
bool& terminationFlag_; bool& terminationFlag_;

View file

@ -1,13 +1,16 @@
#include "BotSession.h" #include "BotSession.h"
#include "Logger.h" #include "Logger.h"
#include "Storage.h"
#include "SubscribeCaseDialog.h" #include "SubscribeCaseDialog.h"
#include <banana/api.hpp> #include <banana/api.hpp>
#include <fmt/core.h> #include <fmt/core.h>
BotSession::BotSession(banana::agent::beast_callback& agent, banana::integer_t userId) BotSession::BotSession(banana::agent::beast_callback& agent,
: agent_(agent), userId_(userId) banana::integer_t userId,
LocalStorage& storage)
: agent_(agent), userId_(userId), storage_(storage)
{ {
LOG(session, "new session created for user {}", userId_); LOG(session, "new session created for user {}", userId_);
} }
@ -20,9 +23,9 @@ void BotSession::processMessage(const banana::api::message_t& message)
{ {
auto text = *message.text; auto text = *message.text;
if (text == "/start") if (text == "/start")
processStartCommand(message); processStartCommand();
else if (text == "/stop") else if (text == "/stop")
processStopCommand(message); processStopCommand();
else if (text == "/subscribe_case") else if (text == "/subscribe_case")
activeDialog_ = makeDialog<SubscribeCaseDialog>(); activeDialog_ = makeDialog<SubscribeCaseDialog>();
else if (activeDialog_) else if (activeDialog_)
@ -30,13 +33,13 @@ void BotSession::processMessage(const banana::api::message_t& message)
if (activeDialog_->processMessage(message)) if (activeDialog_->processMessage(message))
{ {
// TODO // TODO
processStartCommand(message); processStartCommand();
} }
} }
else else
{ {
// TODO // TODO
processStartCommand(message); processStartCommand();
} }
} }
else else
@ -45,9 +48,21 @@ void BotSession::processMessage(const banana::api::message_t& message)
void BotSession::processCallbackQuery(const banana::api::callback_query_t& query) void BotSession::processCallbackQuery(const banana::api::callback_query_t& query)
{ {
if (query.data)
{
if (activeDialog_)
{
if (activeDialog_->processCallbackQuery(query))
processStartCommand();
}
else
LOGE(session, "skip callback query, because there is no active dialog");
}
else
LOGE(session, "skip callback query without data");
} }
void BotSession::processStartCommand(const banana::api::message_t& message) void BotSession::processStartCommand()
{ {
activeDialog_.reset(); activeDialog_.reset();
@ -59,7 +74,7 @@ void BotSession::processStartCommand(const banana::api::message_t& message)
[](const banana::expected<banana::api::message_t>& message) {}); [](const banana::expected<banana::api::message_t>& message) {});
} }
void BotSession::processStopCommand(const banana::api::message_t& message) void BotSession::processStopCommand()
{ {
activeDialog_.reset(); activeDialog_.reset();
} }

View file

@ -1,6 +1,8 @@
#ifndef COURT_MONITOR_BOT_SESSION_H #ifndef COURT_MONITOR_BOT_SESSION_H
#define COURT_MONITOR_BOT_SESSION_H #define COURT_MONITOR_BOT_SESSION_H
#include "Storage.h"
#include <banana/agent/beast.hpp> #include <banana/agent/beast.hpp>
#include <banana/types_fwd.hpp> #include <banana/types_fwd.hpp>
#include <banana/utils/basic_types.hpp> #include <banana/utils/basic_types.hpp>
@ -12,7 +14,7 @@ class Dialog;
class BotSession final class BotSession final
{ {
public: public:
BotSession(banana::agent::beast_callback& agent, banana::integer_t userId); BotSession(banana::agent::beast_callback& agent, banana::integer_t userId, LocalStorage& storage);
~BotSession(); ~BotSession();
void processMessage(const banana::api::message_t& message); void processMessage(const banana::api::message_t& message);
@ -22,14 +24,15 @@ private:
template <class T> template <class T>
std::unique_ptr<Dialog> makeDialog() std::unique_ptr<Dialog> makeDialog()
{ {
return std::make_unique<T>(agent_, userId_); return std::make_unique<T>(agent_, userId_, storage_);
} }
void processStartCommand(const banana::api::message_t& message); void processStartCommand();
void processStopCommand(const banana::api::message_t& message); void processStopCommand();
banana::agent::beast_callback& agent_; banana::agent::beast_callback& agent_;
banana::integer_t userId_; banana::integer_t userId_;
LocalStorage& storage_;
std::unique_ptr<Dialog> activeDialog_; std::unique_ptr<Dialog> activeDialog_;
}; };

View file

@ -1,5 +1,7 @@
#include "CourtApi.h" #include "CourtApi.h"
#include "Logger.h"
#include <fmt/format.h> #include <fmt/format.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -9,7 +11,6 @@
#include <boost/certify/extensions.hpp> #include <boost/certify/extensions.hpp>
#include <boost/certify/https_verification.hpp> #include <boost/certify/https_verification.hpp>
#include <iostream>
#include <thread> #include <thread>
const char* serverDomain = "mirsud.spb.ru"; const char* serverDomain = "mirsud.spb.ru";
@ -24,15 +25,16 @@ ssl_stream connect(boost::asio::io_context& asioContext, const std::string& host
sslContext.set_verify_mode(boost::asio::ssl::verify_peer | sslContext.set_verify_mode(boost::asio::ssl::verify_peer |
boost::asio::ssl::verify_fail_if_no_peer_cert); boost::asio::ssl::verify_fail_if_no_peer_cert);
sslContext.set_default_verify_paths(); sslContext.set_default_verify_paths();
sslContext.load_verify_file("ISRG_X1.pem");
boost::certify::enable_native_https_server_verification(sslContext); 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);
auto const results = resolver.resolve(hostname, "https"); auto const results = resolver.resolve(hostname, "https");
boost::beast::get_lowest_layer(stream).connect(results);
boost::certify::set_server_hostname(stream, hostname); boost::certify::set_server_hostname(stream, hostname);
boost::certify::sni_hostname(stream, hostname); boost::certify::sni_hostname(stream, hostname);
boost::beast::get_lowest_layer(stream).connect(results);
stream.handshake(boost::asio::ssl::stream_base::client); stream.handshake(boost::asio::ssl::stream_base::client);
return stream; return stream;
@ -56,14 +58,11 @@ std::pair<int, std::string> get(ssl_stream& stream,
request.body() = std::move(*payload); request.body() = std::move(*payload);
} }
std::cout << "tx: " << request << std::endl;
boost::beast::http::write(stream, request); boost::beast::http::write(stream, request);
boost::beast::http::response<boost::beast::http::string_body> response; boost::beast::http::response<boost::beast::http::string_body> response;
boost::beast::flat_buffer buffer; boost::beast::flat_buffer buffer;
boost::beast::http::read(stream, buffer, response); boost::beast::http::read(stream, buffer, response);
std::cout << "rx: " << response << std::endl;
return {response.base().result_int(), response.body()}; return {response.base().result_int(), response.body()};
} }
@ -83,27 +82,58 @@ 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(boost::asio::io_context& asioContext, CaseDetails getCaseDetails(boost::asio::io_context& asioContext, const std::string_view& caseNumber)
int courtId,
const std::string_view& caseNumber)
{ {
ssl_stream stream = connect(asioContext, serverDomain); ssl_stream stream = connect(asioContext, serverDomain);
int status; int status;
std::string result; std::string result;
std::tie(status, result) = std::tie(status, result) =
get(stream, serverDomain, get(stream, serverDomain, fmt::format("/cases/api/detail/?id={}", caseNumber));
fmt::format("/cases/api/detail/?id={}&court_site_id={}", caseNumber, courtId));
if (status == 200) if (status == 200)
{ {
auto uuid = nlohmann::json::parse(result).at("id").get<std::string>(); auto uuid = nlohmann::json::parse(result).at("id").get<std::string>();
for (int i = 0; i < 10; i++) for (int i = 0; i < 5; i++)
{ {
auto results = getResults(stream, uuid); auto response = getResults(stream, uuid);
bool finished = results.at("finished").get<bool>(); if (response.at("finished").get<bool>())
if (finished) {
return results.at("result"); auto& results = response.at("result");
LOG(court, results.dump());
CaseDetails details;
details.id = results["id"].get<std::string>();
details.courtNumber = results["court_number"].get<std::string>();
details.name = results["name"].get<std::string>();
details.description = results["description"].get<std::string>();
details.url =
fmt::format("https://{}{}", serverDomain, results["url"].get<std::string>());
details.districtName = results["district_name"].get<std::string>();
details.judgeName = results["judge"].get<std::string>();
for (const auto& participant : results["participants"])
{
CaseParticipant p;
p.title = participant["title"].get<std::string>();
p.name = participant["name"].get<std::string>();
details.participants.push_back(std::move(p));
}
for (const auto& obj : results["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>();
details.history.push_back(std::move(item));
}
return details;
}
else else
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }
@ -113,20 +143,3 @@ nlohmann::json getCaseDetails(boost::asio::io_context& asioContext,
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;
}

View file

@ -9,6 +9,12 @@
#include <string_view> #include <string_view>
#include <vector> #include <vector>
struct CaseParticipant
{
std::string title;
std::string name;
};
struct CaseHistoryItem struct CaseHistoryItem
{ {
std::string date; std::string date;
@ -18,12 +24,29 @@ struct CaseHistoryItem
std::string publishTime; std::string publishTime;
}; };
nlohmann::json findCases(boost::asio::io_context& asioContext, const std::string_view& name); struct CaseDetails
{
std::string id;
std::string courtNumber;
std::string name;
std::string description;
std::string url;
nlohmann::json getCaseDetails(boost::asio::io_context& asioContext, std::string districtName;
int courtId, std::string judgeName;
const std::string_view& caseNumber); std::vector<CaseParticipant> participants;
std::vector<CaseHistoryItem> parseHistory(const nlohmann::json& details); std::string status;
std::vector<CaseHistoryItem> history;
std::string createdDate;
std::string acceptedDate;
std::string judicialUid;
};
std::vector<CaseDetails> findCases(boost::asio::io_context& asioContext,
const std::string_view& name);
CaseDetails getCaseDetails(boost::asio::io_context& asioContext, const std::string_view& caseNumber);
#endif // COURT_MONITOR_COURT_API_H #endif // COURT_MONITOR_COURT_API_H

View file

@ -2,8 +2,7 @@
#include "Logger.h" #include "Logger.h"
Dialog::Dialog(banana::agent::beast_callback& agent, banana::integer_t userId, const char* name) Dialog::Dialog(banana::integer_t userId, const char* name) : userId_(userId), name_(name)
: agent_(agent), userId_(userId), name_(name)
{ {
LOG(dialog, "{} dialog created for user {}", name_, userId_); LOG(dialog, "{} dialog created for user {}", name_, userId_);
} }
@ -12,13 +11,3 @@ Dialog::~Dialog()
{ {
LOG(dialog, "{} dialog for user {} destroyed", name_, userId_); LOG(dialog, "{} dialog for user {} destroyed", name_, userId_);
} }
banana::agent::beast_callback& Dialog::getAgent() const
{
return agent_;
}
banana::integer_t Dialog::getUserId() const
{
return userId_;
}

View file

@ -1,25 +1,20 @@
#ifndef COURT_MONITOR_DIALOG_H #ifndef COURT_MONITOR_DIALOG_H
#define COURT_MONITOR_DIALOG_H #define COURT_MONITOR_DIALOG_H
#include <banana/agent/beast.hpp>
#include <banana/types_fwd.hpp> #include <banana/types_fwd.hpp>
#include <banana/utils/basic_types.hpp> #include <banana/utils/basic_types.hpp>
class Dialog class Dialog
{ {
public: public:
Dialog(banana::agent::beast_callback& agent, banana::integer_t userId, const char* name); Dialog(banana::integer_t userId, const char* name);
virtual ~Dialog(); virtual ~Dialog();
[[nodiscard]] banana::agent::beast_callback& getAgent() const;
[[nodiscard]] banana::integer_t getUserId() const;
// Возвращают true, если диалог завершен. // Возвращают true, если диалог завершен.
virtual bool processMessage(const banana::api::message_t& message) = 0; virtual bool processMessage(const banana::api::message_t& message) = 0;
virtual bool processCallbackQuery(const banana::api::callback_query_t& query) = 0; virtual bool processCallbackQuery(const banana::api::callback_query_t& query) = 0;
private: private:
banana::agent::beast_callback& agent_;
banana::integer_t userId_; banana::integer_t userId_;
const char* name_; const char* name_;
}; };

View file

@ -2,6 +2,7 @@
#define COURT_MONITOR_DIALOG_HELPERS_H #define COURT_MONITOR_DIALOG_HELPERS_H
#include "Logger.h" #include "Logger.h"
#include "Storage.h"
#include <banana/types.hpp> #include <banana/types.hpp>
@ -13,8 +14,13 @@ namespace statechart = boost::statechart;
template <class MostDerived, class InitialState> template <class MostDerived, class InitialState>
struct StateMachine : public statechart::state_machine<MostDerived, InitialState> struct StateMachine : public statechart::state_machine<MostDerived, InitialState>
{ {
explicit StateMachine(Dialog& dialog) : dialog(dialog) {} explicit StateMachine(banana::agent::beast_callback& agent, banana::integer_t userId)
Dialog& dialog; : agent(agent), userId(userId)
{
}
banana::agent::beast_callback& agent;
banana::integer_t userId;
}; };
struct BasicState struct BasicState

31
ISRG_X1.pem Normal file
View file

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

View file

@ -5,10 +5,13 @@
#include "Logger.h" #include "Logger.h"
#include <banana/api.hpp> #include <banana/api.hpp>
#include <nlohmann/json.hpp>
#include <boost/statechart/custom_reaction.hpp> #include <boost/statechart/custom_reaction.hpp>
#include <boost/statechart/event.hpp> #include <boost/statechart/event.hpp>
#include <regex>
namespace { namespace {
// clang-format off // clang-format off
@ -17,6 +20,7 @@ namespace {
struct WaitingForInput; struct WaitingForInput;
struct GettingCaseDetails; struct GettingCaseDetails;
struct WaitingForConfirmation; struct WaitingForConfirmation;
struct Subscribed;
// События // События
struct CaseDetailsFetched : statechart::event<CaseDetailsFetched> { }; struct CaseDetailsFetched : statechart::event<CaseDetailsFetched> { };
@ -28,7 +32,12 @@ struct SubscriptionConfirmed : statechart::event<SubscriptionConfirmed> { };
struct SubscribeCaseStateMachine : StateMachine<SubscribeCaseStateMachine, WaitingForInput> struct SubscribeCaseStateMachine : StateMachine<SubscribeCaseStateMachine, WaitingForInput>
{ {
using StateMachine::StateMachine; SubscribeCaseStateMachine(banana::agent::beast_callback& agent, long userId, LocalStorage& storage)
: StateMachine(agent, userId), storage(storage)
{
}
LocalStorage& storage;
std::string caseNumber;
}; };
namespace { namespace {
@ -39,24 +48,122 @@ struct WaitingForInput : State<WaitingForInput, SubscribeCaseStateMachine>
explicit WaitingForInput(const my_context& ctx) : State(ctx, "WaitingForInput") explicit WaitingForInput(const my_context& ctx) : State(ctx, "WaitingForInput")
{ {
auto& dialog = context<SubscribeCaseStateMachine>().dialog; auto& machine = context<SubscribeCaseStateMachine>();
std::string text = "Введите номер дела"; std::string text = "Введите номер дела...";
banana::api::send_message(dialog.getAgent(), banana::api::send_message(machine.agent,
{.chat_id = dialog.getUserId(), .text = std::move(text)}, {.chat_id = machine.userId, .text = std::move(text)}, [](auto) {});
[](auto) {});
} }
statechart::result react(const NewMessageEvent& event) { return transit<GettingCaseDetails>(); } statechart::result react(const NewMessageEvent& event)
{
auto& machine = context<SubscribeCaseStateMachine>();
const std::regex rex(R"(\d-(?:\d+)/(?:\d){4}-(?:\d+))");
std::smatch captures;
if (std::regex_match(*event.message.text, captures, rex))
{
machine.caseNumber = *event.message.text;
return transit<GettingCaseDetails>();
}
else
{
std::string text =
"Некорректный формат номера дела!\n"
"Попробуйте еще раз.";
banana::api::send_message(
machine.agent, {.chat_id = machine.userId, .text = std::move(text)}, [](auto) {});
return discard_event();
}
}
}; };
struct GettingCaseDetails : State<GettingCaseDetails, SubscribeCaseStateMachine, true> struct GettingCaseDetails : State<GettingCaseDetails, SubscribeCaseStateMachine>
{ {
explicit GettingCaseDetails(const my_context& ctx) : State(ctx, "GettingCaseDetails") {} using reactions = statechart::custom_reaction<CaseDetailsFetched>;
explicit GettingCaseDetails(const my_context& ctx) : State(ctx, "GettingCaseDetails")
{
auto& machine = context<SubscribeCaseStateMachine>();
boost::asio::io_context ioContext;
try
{
auto details = getCaseDetails(ioContext, machine.caseNumber);
std::string text;
fmt::format_to(std::back_inserter(text), "Проверьте информацию:\n{}\n", details.name);
for (const auto& participant : details.participants)
fmt::format_to(std::back_inserter(text), "{}: {}\n", participant.title,
participant.name);
fmt::format_to(std::back_inserter(text), "Судья: {}", details.judgeName);
banana::api::inline_keyboard_markup_t keyboard;
keyboard.inline_keyboard.resize(1);
keyboard.inline_keyboard[0].resize(2);
keyboard.inline_keyboard[0][0].text = "Верно";
keyboard.inline_keyboard[0][0].callback_data = "yes";
keyboard.inline_keyboard[0][1].text = "Отмена";
keyboard.inline_keyboard[0][1].callback_data = "no";
banana::api::send_message(
machine.agent,
{.chat_id = machine.userId, .text = std::move(text), .reply_markup = keyboard},
[](auto) {});
post_event(CaseDetailsFetched());
}
catch (const std::exception& e)
{
LOGE(dialog, e.what());
// TODO ???
}
}
statechart::result react(const CaseDetailsFetched& event)
{
return transit<WaitingForConfirmation>();
}
}; };
struct WaitingForConfirmation : State<WaitingForConfirmation, SubscribeCaseStateMachine> struct WaitingForConfirmation : State<WaitingForConfirmation, SubscribeCaseStateMachine>
{ {
using reactions = statechart::custom_reaction<NewCallbackQueryEvent>;
explicit WaitingForConfirmation(const my_context& ctx) : State(ctx, "WaitingForConfirmation") {} explicit WaitingForConfirmation(const my_context& ctx) : State(ctx, "WaitingForConfirmation") {}
statechart::result react(const NewCallbackQueryEvent& event)
{
auto& machine = context<SubscribeCaseStateMachine>();
if (event.query.data)
{
if (event.query.message)
banana::api::edit_message_reply_markup(
machine.agent,
{.chat_id = event.query.message->chat.id,
.message_id = event.query.message->message_id},
[](banana::expected<banana::variant_t<banana::api::message_t, banana::boolean_t>> result)
{
if (!result)
LOGE(dialog, result.error());
});
if (*event.query.data == "yes")
{
// TODO
return transit<Subscribed>();
}
else if (*event.query.data == "no")
{
return transit<WaitingForInput>();
}
}
return discard_event();
}
};
struct Subscribed : State<Subscribed, SubscribeCaseStateMachine, true>
{
explicit Subscribed(const my_context& ctx) : State(ctx, "Subscribed") {}
}; };
} // namespace } // namespace
@ -64,9 +171,10 @@ struct WaitingForConfirmation : State<WaitingForConfirmation, SubscribeCaseState
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
SubscribeCaseDialog::SubscribeCaseDialog(banana::agent::beast_callback& agent, SubscribeCaseDialog::SubscribeCaseDialog(banana::agent::beast_callback& agent,
banana::integer_t userId) banana::integer_t userId,
: Dialog(agent, userId, "SubscribeCase"), LocalStorage& storage)
machine_(std::make_unique<SubscribeCaseStateMachine>(*this)) : Dialog(userId, "SubscribeCase"),
machine_(std::make_unique<SubscribeCaseStateMachine>(agent, userId, storage))
{ {
machine_->initiate(); machine_->initiate();
} }

View file

@ -2,13 +2,18 @@
#define COURT_MONITOR_SUBSCRIBE_CASE_DIALOG_H #define COURT_MONITOR_SUBSCRIBE_CASE_DIALOG_H
#include "Dialog.h" #include "Dialog.h"
#include "Storage.h"
#include <banana/agent/beast.hpp>
struct SubscribeCaseStateMachine; struct SubscribeCaseStateMachine;
class SubscribeCaseDialog : public Dialog class SubscribeCaseDialog : public Dialog
{ {
public: public:
SubscribeCaseDialog(banana::agent::beast_callback& agent, banana::integer_t userId); SubscribeCaseDialog(banana::agent::beast_callback& agent,
banana::integer_t userId,
LocalStorage& storage);
~SubscribeCaseDialog() override; ~SubscribeCaseDialog() override;
bool processMessage(const banana::api::message_t& message) override; bool processMessage(const banana::api::message_t& message) override;

View file

@ -27,14 +27,11 @@ void processAllSubscriptions(LocalStorage& storage, Bot& bot)
for (auto& counter : subscription.counters) for (auto& counter : subscription.counters)
{ {
LOG(main, "** Processing case {}", counter.caseNumber); LOG(main, "** Processing case {}", counter.caseNumber);
auto details = getCaseDetails(asioContext, counter.courtId, counter.caseNumber); auto details = getCaseDetails(asioContext, counter.caseNumber);
LOG(main, details.dump()); for (std::size_t i = counter.value; i < details.history.size(); i++)
auto url = details["url"].get<std::string>(); bot.notifyUser(subscription.userId, counter.caseNumber, details.url,
details.history[i]);
auto history = parseHistory(details); counter.value = details.history.size();
for (std::size_t i = counter.value; i < history.size(); i++)
bot.notifyUser(subscription.userId, counter.caseNumber, url, history[i]);
counter.value = history.size();
} }
} }
catch (const std::exception& e) catch (const std::exception& e)