mirror of
https://github.com/UltraCoderRU/court_monitor.git
synced 2026-01-28 02:15:12 +00:00
Добавлены сессии и диалоги.
This commit is contained in:
parent
1b325a343c
commit
4309860ddb
12 changed files with 368 additions and 33 deletions
|
|
@ -109,7 +109,7 @@ SpacesInConditionalStatement: false
|
|||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: c++14
|
||||
Standard: c++20
|
||||
StatementMacros: ['Q_UNUSED']
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
|
|
|
|||
64
Bot.cpp
64
Bot.cpp
|
|
@ -16,15 +16,19 @@ Bot::Bot(boost::asio::io_context& asioContext, LocalStorage& storage, bool& term
|
|||
terminationFlag_(terminationFlag),
|
||||
agent_(storage.token, asioContext, sslContext)
|
||||
{
|
||||
setupCommands();
|
||||
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.commands.push_back(
|
||||
banana::api::bot_command_t{"/subscribe_case", "Подписаться на обновления дела"});
|
||||
args.commands.push_back(
|
||||
banana::api::bot_command_t{"/unsubscribe_case", "Отписаться от обновлений дела"});
|
||||
args.commands.push_back(banana::api::bot_command_t{"/check_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)
|
||||
|
|
@ -68,39 +72,53 @@ void Bot::getUpdates()
|
|||
getUpdates();
|
||||
}
|
||||
else
|
||||
LOG(bot, "failed to get updates: {}", updates.error());
|
||||
LOGE(bot, "failed to get updates: {}", updates.error());
|
||||
};
|
||||
|
||||
banana::api::get_updates(agent_, {.offset = updatesOffset_, .timeout = 50}, std::move(handler));
|
||||
banana::api::get_updates(
|
||||
agent_,
|
||||
{.offset = updatesOffset_,
|
||||
.timeout = 50,
|
||||
.allowed_updates = banana::array_t<banana::string_t>{"message", "callback_query"}},
|
||||
std::move(handler));
|
||||
}
|
||||
|
||||
void Bot::processUpdate(const banana::api::update_t& update)
|
||||
{
|
||||
if (update.message)
|
||||
{
|
||||
banana::integer_t userId = update.message->from->id;
|
||||
|
||||
if (update.message->text)
|
||||
{
|
||||
LOG(bot, "rx: {}\n", *update.message->text);
|
||||
if (*update.message->text == "/start")
|
||||
processStartCommand(*update.message);
|
||||
else if (*update.message->text == "/subscribe_case")
|
||||
processSubscribeCaseCommand(*update.message);
|
||||
LOG(bot, "incoming message: user={} text='{}'", userId, *update.message->text);
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG(bot, "skip message without text"); // TODO ответить
|
||||
}
|
||||
else
|
||||
LOGE(bot, "skip unknown update type");
|
||||
}
|
||||
LOG(bot, "incoming message: user={} (not text)", userId);
|
||||
|
||||
void Bot::processStartCommand(const banana::api::message_t& message)
|
||||
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));
|
||||
}
|
||||
sessionIt->second.processMessage(*update.message);
|
||||
}
|
||||
else if (update.callback_query)
|
||||
{
|
||||
banana::integer_t userId = update.callback_query->from.id;
|
||||
LOG(bot, "incoming callback query: user={} data='{}'", userId, *update.callback_query->data);
|
||||
|
||||
void Bot::processSubscribeCaseCommand(const banana::api::message_t& message)
|
||||
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));
|
||||
}
|
||||
sessionIt->second.processCallbackQuery(*update.callback_query);
|
||||
}
|
||||
else
|
||||
LOG(bot, "skip unknown update type");
|
||||
}
|
||||
|
|
|
|||
11
Bot.h
11
Bot.h
|
|
@ -1,35 +1,36 @@
|
|||
#ifndef COURT_MONITOR_BOT_H
|
||||
#define COURT_MONITOR_BOT_H
|
||||
|
||||
#include "BotSession.h"
|
||||
#include "CourtApi.h"
|
||||
#include "Storage.h"
|
||||
|
||||
#include <banana/agent/beast.hpp>
|
||||
#include <banana/types_fwd.hpp>
|
||||
|
||||
#include <map>
|
||||
|
||||
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 setupCommands();
|
||||
|
||||
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;
|
||||
std::map<banana::integer_t, BotSession> sessions_;
|
||||
};
|
||||
|
||||
#endif // COURT_MONITOR_BOT_H
|
||||
|
|
|
|||
65
BotSession.cpp
Normal file
65
BotSession.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#include "BotSession.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "SubscribeCaseDialog.h"
|
||||
|
||||
#include <banana/api.hpp>
|
||||
#include <fmt/core.h>
|
||||
|
||||
BotSession::BotSession(banana::agent::beast_callback& agent, banana::integer_t userId)
|
||||
: agent_(agent), userId_(userId)
|
||||
{
|
||||
LOG(session, "new session created for user {}", userId_);
|
||||
}
|
||||
|
||||
BotSession::~BotSession() = default;
|
||||
|
||||
void BotSession::processMessage(const banana::api::message_t& message)
|
||||
{
|
||||
if (message.text)
|
||||
{
|
||||
auto text = *message.text;
|
||||
if (text == "/start")
|
||||
processStartCommand(message);
|
||||
else if (text == "/stop")
|
||||
processStopCommand(message);
|
||||
else if (text == "/subscribe_case")
|
||||
activeDialog_ = makeDialog<SubscribeCaseDialog>();
|
||||
else if (activeDialog_)
|
||||
{
|
||||
if (activeDialog_->processMessage(message))
|
||||
{
|
||||
// TODO
|
||||
processStartCommand(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
processStartCommand(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
LOGE(session, "skip message without text"); // TODO ответить
|
||||
}
|
||||
|
||||
void BotSession::processCallbackQuery(const banana::api::callback_query_t& query)
|
||||
{
|
||||
}
|
||||
|
||||
void BotSession::processStartCommand(const banana::api::message_t& message)
|
||||
{
|
||||
activeDialog_.reset();
|
||||
|
||||
std::string text =
|
||||
"Вас приветствует неофициальный бот Мирового Суда г. Санкт-Петербурга.\n\n"
|
||||
"Выберите в меню желаемое действие.";
|
||||
|
||||
banana::api::send_message(agent_, {.chat_id = userId_, .text = std::move(text)},
|
||||
[](const banana::expected<banana::api::message_t>& message) {});
|
||||
}
|
||||
|
||||
void BotSession::processStopCommand(const banana::api::message_t& message)
|
||||
{
|
||||
activeDialog_.reset();
|
||||
}
|
||||
36
BotSession.h
Normal file
36
BotSession.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef COURT_MONITOR_BOT_SESSION_H
|
||||
#define COURT_MONITOR_BOT_SESSION_H
|
||||
|
||||
#include <banana/agent/beast.hpp>
|
||||
#include <banana/types_fwd.hpp>
|
||||
#include <banana/utils/basic_types.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Dialog;
|
||||
|
||||
class BotSession final
|
||||
{
|
||||
public:
|
||||
BotSession(banana::agent::beast_callback& agent, banana::integer_t userId);
|
||||
~BotSession();
|
||||
|
||||
void processMessage(const banana::api::message_t& message);
|
||||
void processCallbackQuery(const banana::api::callback_query_t& query);
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
std::unique_ptr<Dialog> makeDialog()
|
||||
{
|
||||
return std::make_unique<T>(agent_, userId_);
|
||||
}
|
||||
|
||||
void processStartCommand(const banana::api::message_t& message);
|
||||
void processStopCommand(const banana::api::message_t& message);
|
||||
|
||||
banana::agent::beast_callback& agent_;
|
||||
banana::integer_t userId_;
|
||||
std::unique_ptr<Dialog> activeDialog_;
|
||||
};
|
||||
|
||||
#endif // COURT_MONITOR_BOT_SESSION_H
|
||||
|
|
@ -12,9 +12,12 @@ add_subdirectory(external)
|
|||
|
||||
add_executable(court_monitor
|
||||
Bot.cpp
|
||||
BotSession.cpp
|
||||
CourtApi.cpp
|
||||
Dialog.cpp
|
||||
Logger.cpp
|
||||
Storage.cpp
|
||||
SubscribeCaseDialog.cpp
|
||||
main.cpp
|
||||
)
|
||||
target_link_libraries(court_monitor
|
||||
|
|
|
|||
24
Dialog.cpp
Normal file
24
Dialog.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "Dialog.h"
|
||||
|
||||
#include "Logger.h"
|
||||
|
||||
Dialog::Dialog(banana::agent::beast_callback& agent, banana::integer_t userId, const char* name)
|
||||
: agent_(agent), userId_(userId), name_(name)
|
||||
{
|
||||
LOG(dialog, "{} dialog created for user {}", name_, userId_);
|
||||
}
|
||||
|
||||
Dialog::~Dialog()
|
||||
{
|
||||
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_;
|
||||
}
|
||||
27
Dialog.h
Normal file
27
Dialog.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef COURT_MONITOR_DIALOG_H
|
||||
#define COURT_MONITOR_DIALOG_H
|
||||
|
||||
#include <banana/agent/beast.hpp>
|
||||
#include <banana/types_fwd.hpp>
|
||||
#include <banana/utils/basic_types.hpp>
|
||||
|
||||
class Dialog
|
||||
{
|
||||
public:
|
||||
Dialog(banana::agent::beast_callback& agent, banana::integer_t userId, const char* name);
|
||||
virtual ~Dialog();
|
||||
|
||||
[[nodiscard]] banana::agent::beast_callback& getAgent() const;
|
||||
[[nodiscard]] banana::integer_t getUserId() const;
|
||||
|
||||
// Возвращают true, если диалог завершен.
|
||||
virtual bool processMessage(const banana::api::message_t& message) = 0;
|
||||
virtual bool processCallbackQuery(const banana::api::callback_query_t& query) = 0;
|
||||
|
||||
private:
|
||||
banana::agent::beast_callback& agent_;
|
||||
banana::integer_t userId_;
|
||||
const char* name_;
|
||||
};
|
||||
|
||||
#endif // COURT_MONITOR_DIALOG_H
|
||||
55
DialogHelpers.h
Normal file
55
DialogHelpers.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef COURT_MONITOR_DIALOG_HELPERS_H
|
||||
#define COURT_MONITOR_DIALOG_HELPERS_H
|
||||
|
||||
#include "Logger.h"
|
||||
|
||||
#include <banana/types.hpp>
|
||||
|
||||
#include <boost/statechart/state.hpp>
|
||||
#include <boost/statechart/state_machine.hpp>
|
||||
|
||||
namespace statechart = boost::statechart;
|
||||
|
||||
template <class MostDerived, class InitialState>
|
||||
struct StateMachine : public statechart::state_machine<MostDerived, InitialState>
|
||||
{
|
||||
explicit StateMachine(Dialog& dialog) : dialog(dialog) {}
|
||||
Dialog& dialog;
|
||||
};
|
||||
|
||||
struct BasicState
|
||||
{
|
||||
[[nodiscard]] virtual bool isFinal() const noexcept = 0;
|
||||
};
|
||||
|
||||
template <class MostDerived, class Context, bool IsFinal = false>
|
||||
class State : public statechart::state<MostDerived, Context>, public BasicState
|
||||
{
|
||||
public:
|
||||
State(typename statechart::state<MostDerived, Context>::my_context ctx, const char* name)
|
||||
: statechart::state<MostDerived, Context>(ctx), name_(name)
|
||||
{
|
||||
LOG(dialog, "entering state {}", name_);
|
||||
}
|
||||
|
||||
~State() { LOG(dialog, "leaving state {}", name_); }
|
||||
|
||||
[[nodiscard]] bool isFinal() const noexcept override { return IsFinal; }
|
||||
|
||||
private:
|
||||
const char* name_;
|
||||
};
|
||||
|
||||
struct NewMessageEvent : statechart::event<NewMessageEvent>
|
||||
{
|
||||
explicit NewMessageEvent(banana::api::message_t message) : message(std::move(message)) {}
|
||||
banana::api::message_t message;
|
||||
};
|
||||
|
||||
struct NewCallbackQueryEvent : statechart::event<NewCallbackQueryEvent>
|
||||
{
|
||||
explicit NewCallbackQueryEvent(banana::api::callback_query_t query) : query(std::move(query)) {}
|
||||
banana::api::callback_query_t query;
|
||||
};
|
||||
|
||||
#endif // COURT_MONITOR_DIALOG_HELPERS_H
|
||||
86
SubscribeCaseDialog.cpp
Normal file
86
SubscribeCaseDialog.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include "SubscribeCaseDialog.h"
|
||||
|
||||
#include "CourtApi.h"
|
||||
#include "DialogHelpers.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#include <banana/api.hpp>
|
||||
|
||||
#include <boost/statechart/custom_reaction.hpp>
|
||||
#include <boost/statechart/event.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
// clang-format off
|
||||
|
||||
// Состояния
|
||||
struct WaitingForInput;
|
||||
struct GettingCaseDetails;
|
||||
struct WaitingForConfirmation;
|
||||
|
||||
// События
|
||||
struct CaseDetailsFetched : statechart::event<CaseDetailsFetched> { };
|
||||
struct SubscriptionConfirmed : statechart::event<SubscriptionConfirmed> { };
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace
|
||||
|
||||
struct SubscribeCaseStateMachine : StateMachine<SubscribeCaseStateMachine, WaitingForInput>
|
||||
{
|
||||
using StateMachine::StateMachine;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
struct WaitingForInput : State<WaitingForInput, SubscribeCaseStateMachine>
|
||||
{
|
||||
using reactions = statechart::custom_reaction<NewMessageEvent>;
|
||||
|
||||
explicit WaitingForInput(const my_context& ctx) : State(ctx, "WaitingForInput")
|
||||
{
|
||||
auto& dialog = context<SubscribeCaseStateMachine>().dialog;
|
||||
std::string text = "Введите номер дела";
|
||||
banana::api::send_message(dialog.getAgent(),
|
||||
{.chat_id = dialog.getUserId(), .text = std::move(text)},
|
||||
[](auto) {});
|
||||
}
|
||||
|
||||
statechart::result react(const NewMessageEvent& event) { return transit<GettingCaseDetails>(); }
|
||||
};
|
||||
|
||||
struct GettingCaseDetails : State<GettingCaseDetails, SubscribeCaseStateMachine, true>
|
||||
{
|
||||
explicit GettingCaseDetails(const my_context& ctx) : State(ctx, "GettingCaseDetails") {}
|
||||
};
|
||||
|
||||
struct WaitingForConfirmation : State<WaitingForConfirmation, SubscribeCaseStateMachine>
|
||||
{
|
||||
explicit WaitingForConfirmation(const my_context& ctx) : State(ctx, "WaitingForConfirmation") {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SubscribeCaseDialog::SubscribeCaseDialog(banana::agent::beast_callback& agent,
|
||||
banana::integer_t userId)
|
||||
: Dialog(agent, userId, "SubscribeCase"),
|
||||
machine_(std::make_unique<SubscribeCaseStateMachine>(*this))
|
||||
{
|
||||
machine_->initiate();
|
||||
}
|
||||
|
||||
SubscribeCaseDialog::~SubscribeCaseDialog() = default;
|
||||
|
||||
bool SubscribeCaseDialog::processMessage(const banana::api::message_t& message)
|
||||
{
|
||||
machine_->process_event(NewMessageEvent{message});
|
||||
return machine_->state_cast<const BasicState&>().isFinal();
|
||||
}
|
||||
|
||||
bool SubscribeCaseDialog::processCallbackQuery(const banana::api::callback_query_t& query)
|
||||
{
|
||||
machine_->process_event(NewCallbackQueryEvent{query});
|
||||
return machine_->state_cast<const BasicState&>().isFinal();
|
||||
}
|
||||
21
SubscribeCaseDialog.h
Normal file
21
SubscribeCaseDialog.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef COURT_MONITOR_SUBSCRIBE_CASE_DIALOG_H
|
||||
#define COURT_MONITOR_SUBSCRIBE_CASE_DIALOG_H
|
||||
|
||||
#include "Dialog.h"
|
||||
|
||||
struct SubscribeCaseStateMachine;
|
||||
|
||||
class SubscribeCaseDialog : public Dialog
|
||||
{
|
||||
public:
|
||||
SubscribeCaseDialog(banana::agent::beast_callback& agent, banana::integer_t userId);
|
||||
~SubscribeCaseDialog() override;
|
||||
|
||||
bool processMessage(const banana::api::message_t& message) override;
|
||||
bool processCallbackQuery(const banana::api::callback_query_t& query) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<SubscribeCaseStateMachine> machine_;
|
||||
};
|
||||
|
||||
#endif // COURT_MONITOR_SUBSCRIBE_CASE_DIALOG_H
|
||||
1
main.cpp
1
main.cpp
|
|
@ -79,7 +79,6 @@ int main()
|
|||
|
||||
// Создать бота
|
||||
Bot bot(asioContext, storage, terminate);
|
||||
bot.setupCommands();
|
||||
|
||||
// Создать таймер ежедневной проверки
|
||||
boost::asio::system_timer checkTimer(asioContext);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue