refactor(tgbot-front): replaced the context usage with a new thread-safe one

This commit is contained in:
Kirill 2025-12-06 05:02:34 +03:00
parent b1c035ae35
commit a6848bb4d7
5 changed files with 55 additions and 56 deletions

View file

@ -3,6 +3,7 @@
#include <vector> #include <vector>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include "constants.hpp"
enum class UserState { enum class UserState {
MAIN_MENU, // Главное меню MAIN_MENU, // Главное меню
@ -50,6 +51,13 @@ public:
// Получить текущий шаг (последний в истории) или std::nullopt, если нет истории // Получить текущий шаг (последний в истории) или std::nullopt, если нет истории
std::optional<NavigationStep> getCurrentStep(int64_t userId) const; std::optional<NavigationStep> getCurrentStep(int64_t userId) const;
// pop последнего состояния. true в случае удачи
bool popStep(int64_t userId);
// Удалить контекст пользователя (например, при логауте) // Удалить контекст пользователя (например, при логауте)
void removeContext(int64_t userId); void removeContext(int64_t userId);
/// @brief Создает контекст начального меню для пользователя
/// @param userId
void createInitContext(int64_t userId);
}; };

View file

@ -4,6 +4,7 @@
#include <structs.hpp> #include <structs.hpp>
#include <unordered_map> #include <unordered_map>
#include "BotToServer.hpp" #include "BotToServer.hpp"
#include "BotUserContext.hpp"
/// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения /// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения
struct HandlerResult { struct HandlerResult {
@ -11,30 +12,6 @@ struct HandlerResult {
TgBot::InlineKeyboardMarkup::Ptr keyboard; TgBot::InlineKeyboardMarkup::Ptr keyboard;
}; };
enum class UserState {
MAIN_MENU, // Главное меню
VIEWING_MY_TITLES, // Список моих тайтлов
AWAITING_TITLE_NAME, // Жду название тайтла для поиска
VIEWING_FOUND_TITLES, // Смотрю найденные тайтлы
VIEWING_TITLE_PAGE, // Смотрю страничку тайтла
AWAITING_REVIEW, // Жду ревью на тайтл
VIEWING_REVIEW_LIST, // Смотрю список ревью на тайтл
VIEWING_REVIEW, // Смотрю (конкретное) ревью на тайтл
VIEWING_DESCRIPTION, // Смотрю описание тайтла
ERROR, // Ошибка состояния
};
struct NavigationStep {
UserState state;
int64_t payload; // ID тайтла, ревью и т.д.
};
struct UserContext {
int64_t userId;
std::vector<NavigationStep> history; // Текущее состояние пользователя + история предыдущих состояний
};
class BotHandlers { class BotHandlers {
public: public:
BotHandlers(TgBot::Api api) : botApi(api) {;} BotHandlers(TgBot::Api api) : botApi(api) {;}
@ -52,18 +29,15 @@ public:
/// @param message обрабатываемое сообщение /// @param message обрабатываемое сообщение
void handleMessage(TgBot::Message::Ptr message); void handleMessage(TgBot::Message::Ptr message);
/// @brief Создает контекст начального меню для пользователя void initUser(int64_t userId);
/// @param chatId id чата пользователя
void createInitContext(int64_t chatId);
private: private:
TgBot::Api botApi; TgBot::Api botApi;
std::unordered_map<int64_t, UserContext> userContexts; BotUserContext contextManager;
BotToServer server_; BotToServer server_;
void handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext& ctx); void handleNavigation(TgBot::CallbackQuery::Ptr query);
void handleError(TgBot::CallbackQuery::Ptr query, UserContext& ctx); void handleError(TgBot::CallbackQuery::Ptr query);
void processCallbackImpl(TgBot::CallbackQuery::Ptr query); void processCallbackImpl(TgBot::CallbackQuery::Ptr query);
@ -73,17 +47,6 @@ private:
/// @return HandlerResult /// @return HandlerResult
/// static HandlerResult returnMyTitles(int64_t userId, int64_t payload); /// static HandlerResult returnMyTitles(int64_t userId, int64_t payload);
/// @brief Вход в новое состояние
/// @param ctx текущий контекст
/// @param newState новое состояние, добавляемое в стек
/// @param payload полезная нагрузка этого состояния
void pushState(UserContext& ctx, UserState newState, int64_t payload);
/// @brief Возврат в предыдущее состояние
/// @param ctx Текущий контекст
/// @return true в случае успеха
bool popState(UserContext& ctx);
/// @brief Уменьшает значение нагрузки с учетом текущего состояния /// @brief Уменьшает значение нагрузки с учетом текущего состояния
/// @param payload Изменяемое значение нагрузки /// @param payload Изменяемое значение нагрузки
/// @param curState Текущее состояние /// @param curState Текущее состояние
@ -105,7 +68,7 @@ private:
/// @brief Отрисовка текущего экрана (соотв. контексту) /// @brief Отрисовка текущего экрана (соотв. контексту)
/// @param ctx - текущий контекст /// @param ctx - текущий контекст
void renderCurrent(TgBot::CallbackQuery::Ptr query, const UserContext& ctx); void renderCurrent(TgBot::CallbackQuery::Ptr query);
/// @brief Логика переходов между контекстами (навигация на следующий шаг) /// @brief Логика переходов между контекстами (навигация на следующий шаг)
/// @param query - запрос /// @param query - запрос

View file

@ -41,3 +41,18 @@ void BotUserContext::removeContext(int64_t userId) {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
userContexts.erase(userId); userContexts.erase(userId);
} }
bool BotUserContext::popStep(int64_t userId) {
std::lock_guard<std::mutex> lock(mtx);
auto it = userContexts.find(userId);
if (it != userContexts.end() && (it->second.history.size() > 1)) {
it->second.history.pop_back();
return true;
}
return false;
}
void BotUserContext::createInitContext(int64_t userId) {
NavigationStep initStep = {UserState::MAIN_MENU, BotConstants::NULL_PAYLOAD};
setContext(userId, {userId, {initStep}});
}

View file

@ -13,7 +13,7 @@ void AnimeBot::setupHandlers() {
bot.getEvents().onCommand("start", [this](TgBot::Message::Ptr message) { bot.getEvents().onCommand("start", [this](TgBot::Message::Ptr message) {
sendMainMenu(message->chat->id); sendMainMenu(message->chat->id);
//TODO: производить инициализацию контекста только после авторизации //TODO: производить инициализацию контекста только после авторизации
handler.createInitContext(message->chat->id); handler.initUser(message->from->id);
}); });
bot.getEvents().onCallbackQuery([this](TgBot::CallbackQuery::Ptr query) { bot.getEvents().onCallbackQuery([this](TgBot::CallbackQuery::Ptr query) {

View file

@ -3,13 +3,15 @@
#include "structs.hpp" #include "structs.hpp"
#include "constants.hpp" #include "constants.hpp"
void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext& ctx) { void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query) {
const auto& current = ctx.history.back(); // текущий экран //const auto& current = ctx.history.back(); // текущий экран
const std::string& data = query->data; const std::string& data = query->data;
int64_t userId = query->from->id;
int64_t chatId = query->message->chat->id; int64_t chatId = query->message->chat->id;
int64_t messageId = query->message->messageId; int64_t messageId = query->message->messageId;
// Пагинация (в списках) // Пагинация (в списках)
/* Временно отключаем, все равно не функционирует :)
if ((data == BotConstants::Callback::LIST_PREV || data == BotConstants::Callback::LIST_NEXT) if ((data == BotConstants::Callback::LIST_PREV || data == BotConstants::Callback::LIST_NEXT)
&& (current.state == UserState::VIEWING_MY_TITLES || current.state == UserState::VIEWING_REVIEW_LIST || && (current.state == UserState::VIEWING_MY_TITLES || current.state == UserState::VIEWING_REVIEW_LIST ||
current.state == UserState::VIEWING_FOUND_TITLES)) { current.state == UserState::VIEWING_FOUND_TITLES)) {
@ -32,35 +34,46 @@ void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext&
renderCurrent(query, ctx); renderCurrent(query, ctx);
return; return;
} }*/
// Обработка back по интерфейсу // Обработка back по интерфейсу
if (data == BotConstants::Callback::NAV_BACK) { if (data == BotConstants::Callback::NAV_BACK) {
if (!popState(ctx)) { if (!contextManager.popStep(userId)) {
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR); sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
return; return;
} }
renderCurrent(query, ctx); renderCurrent(query);
return; return;
} }
// Переходы вперёд (push) // Переходы вперёд (push)
auto newStepOpt = computeNextStep(query, current); std::optional<NavigationStep> currentStep = contextManager.getCurrentStep(userId);
if(!currentStep.has_value()) {
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
return;
}
auto newStepOpt = computeNextStep(query, currentStep.value());
if (!newStepOpt.has_value()) { if (!newStepOpt.has_value()) {
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR); sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
return; return;
} }
ctx.history.push_back(*newStepOpt); contextManager.pushNavigationStep(userId, newStepOpt.value());
renderCurrent(query, ctx); renderCurrent(query);
} }
void BotHandlers::renderCurrent(TgBot::CallbackQuery::Ptr query, const UserContext& ctx) { void BotHandlers::renderCurrent(TgBot::CallbackQuery::Ptr query) {
const auto& step = ctx.history.back(); int64_t userId = query->from->id;
//int64_t userId = query->from->id;
int64_t chatId = query->message->chat->id; int64_t chatId = query->message->chat->id;
int64_t messageId = query->message->messageId; int64_t messageId = query->message->messageId;
switch (step.state) {
auto step = contextManager.getCurrentStep(userId);
if(!step.has_value()) {;
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
return;
}
switch (step.value().state) {
case UserState::MAIN_MENU: case UserState::MAIN_MENU:
editMessage(chatId, messageId, showMainMenu()); editMessage(chatId, messageId, showMainMenu());
return; return;