diff --git a/modules/bot/front/include/constants.hpp b/modules/bot/front/include/constants.hpp index e384186..8fee9d5 100644 --- a/modules/bot/front/include/constants.hpp +++ b/modules/bot/front/include/constants.hpp @@ -33,5 +33,6 @@ namespace BotConstants { namespace Text { const std::string MAIN_MENU = "Вас приветствует nyanimedb бот:)\nЧего будем делать?"; const std::string SAD_ERROR = "У нас что-то случилось:(\nМы обязательно скоро исправимся"; + const std::string AUTH_ERROR = "Проблемы с авторизацией, попробуйте авторизоваться повторно"; } } diff --git a/modules/bot/front/include/handlers.hpp b/modules/bot/front/include/handlers.hpp index c3753b6..572984d 100644 --- a/modules/bot/front/include/handlers.hpp +++ b/modules/bot/front/include/handlers.hpp @@ -30,6 +30,7 @@ struct NavigationStep { struct UserContext { + int64_t userId; std::vector history; // Текущее состояние пользователя + история предыдущих состояний }; @@ -50,6 +51,10 @@ public: /// @param message обрабатываемое сообщение void handleMessage(TgBot::Message::Ptr message); + /// @brief Создает контекст начального меню для пользователя + /// @param chatId id чата пользователя + void createInitContext(int64_t chatId); + private: TgBot::Api botApi; std::unordered_map userContexts; @@ -60,8 +65,9 @@ private: /// @brief Получить очередную страницу тайтлов из списка пользователя /// @param userId Идентификатор пользователя + /// @param payload Полезная нагрузка /// @return HandlerResult - static HandlerResult returnMyTitles(int64_t userId); + static HandlerResult returnMyTitles(int64_t userId, int64_t payload); /// @brief Вход в новое состояние /// @param ctx текущий контекст @@ -83,4 +89,33 @@ private: /// @param payload Изменяемое значение нагрузки /// @param curState Текущее состояние void increasePayload(int64_t& payload, const UserState curState); + + /// @brief Редактирует текущее сообщение в диалоге с пользователем + /// @details Меняет текст сообщения и клавиатуру на те, что передаются + /// в аргументе response. Информацию об id чата и изменяемого сообщения + /// забирает из query, который возвращается с callback'ом после нажатия + /// кнопки в интерфейсе + /// @param query Callback запрос + /// @param response Параметры ответа: клавиатура и текст + void editMessage(TgBot::CallbackQuery::Ptr query, HandlerResult response); + + /// @brief Отрисовка текущего экрана (соотв. контексту) + /// @param ctx - текущий контекст + /// @return HandlerResult для нового состояния сообщения + HandlerResult renderCurrent(const UserContext& ctx); + + /// @brief Логика переходов между контекстами (навигация на следующий шаг) + /// @param query - запрос + /// @param current - текущий шаг навигации + /// @return следующий NavigationStep при успехе (std::nullopt в случае ошибки) + std::optional computeNextStep(const TgBot::CallbackQuery::Ptr& query, + const NavigationStep& current); + + /// @brief Получить состояние страницы главного меню + /// @return HandlerResult с параметрами главного меню + HandlerResult showMainMenu(); + + /// @brief Посылает интерфейс обработки ошибки на callback запрос + /// @param query запрос + void sendError(TgBot::CallbackQuery::Ptr query, const std::string& errText); }; diff --git a/modules/bot/front/src/KeyboardFactory.cpp b/modules/bot/front/src/KeyboardFactory.cpp index fc1a3fd..cf2500c 100644 --- a/modules/bot/front/src/KeyboardFactory.cpp +++ b/modules/bot/front/src/KeyboardFactory.cpp @@ -14,6 +14,7 @@ TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMainMenu() { return keyboard; } +// TODO: Переписать с учетом констант на количество отображаемых тайтлов и нового callback'a TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMyTitles(std::vector titles) { auto keyboard = std::make_shared<TgBot::InlineKeyboardMarkup>(); std::vector<TgBot::InlineKeyboardButton::Ptr> row; @@ -39,13 +40,23 @@ TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMyTitles(std::vector<Tit if(counter % 2 == 1) { auto button = std::make_shared<TgBot::InlineKeyboardButton>(); button->text = BotConstants::Button::PREV; - button->callbackData = BotConstants::Callback::LIST_PREV + ':' + std::to_string(titles[0].num); + if(titles[0].num == 1) { + button->callbackData = BotConstants::Callback::NAV_BACK; + } + else { + button->callbackData = BotConstants::Callback::LIST_PREV + ':' + std::to_string(titles[0].num); + } layout[counter / 2].push_back(button); } else { auto button_prev = std::make_shared<TgBot::InlineKeyboardButton>(); button_prev->text = BotConstants::Button::PREV; - button_prev->callbackData = BotConstants::Callback::LIST_PREV + ':' + std::to_string(titles[0].num); + if(titles[0].num == 1) { + button_prev->callbackData = BotConstants::Callback::NAV_BACK; + } + else { + button_prev->callbackData = BotConstants::Callback::LIST_PREV + ':' + std::to_string(titles[0].num); + } auto button_next = std::make_shared<TgBot::InlineKeyboardButton>(); button_next->text = BotConstants::Button::NEXT; button_next->callbackData = BotConstants::Callback::LIST_NEXT + ':' + std::to_string(titles[5].num); diff --git a/modules/bot/front/src/front.cpp b/modules/bot/front/src/front.cpp index 9ee010c..4f463e1 100644 --- a/modules/bot/front/src/front.cpp +++ b/modules/bot/front/src/front.cpp @@ -12,6 +12,8 @@ AnimeBot::AnimeBot(const std::string& token) void AnimeBot::setupHandlers() { bot.getEvents().onCommand("start", [this](TgBot::Message::Ptr message) { sendMainMenu(message->chat->id); + //TODO: производить инициализацию контекста только после авторизации + handler.createInitContext(message->chat->id); }); bot.getEvents().onCallbackQuery([this](TgBot::CallbackQuery::Ptr query) { diff --git a/modules/bot/front/src/handlers.cpp b/modules/bot/front/src/handlers.cpp index a3633eb..5bdef9d 100644 --- a/modules/bot/front/src/handlers.cpp +++ b/modules/bot/front/src/handlers.cpp @@ -20,7 +20,7 @@ void BotHandlers::handleCallback(TgBot::CallbackQuery::Ptr query) { processCallbackImpl(query); } -HandlerResult BotHandlers::returnMyTitles(int64_t userId) { +HandlerResult BotHandlers::returnMyTitles(int64_t userId, int64_t payload) { // Здесь должен происходить запрос на сервер std::vector<Title> titles = {{123, "Школа мертвяков", "", 1}, {321, "KissXsis", "", 2}}; @@ -31,44 +31,6 @@ HandlerResult BotHandlers::returnMyTitles(int64_t userId) { return result; } -void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query) { - int64_t userId = query->from->id; - auto it = userContexts.find(userId); - if (it == userContexts.end()) { - botApi.sendMessage(query->message->chat->id, BotConstants::Text::SAD_ERROR); - return; - } - - UserContext& ctx = it->second; - const std::string& data = query->data; - - //HandlerResult response; - //UserContext newCtx; - - auto [response, newCtx] = newStateNavigation(query, ctx); - switch (ctx.state) { - case UserState::VIEWING_MY_TITLES: - response = BotHandlers::returnMyTitles(ctx.cursor); - break; - case UserState::VIEWING_REVIEW_LIST: - response = BotHandlers::returnReviewList(ctx.cursor); - break; - default: - botApi.sendMessage(query->message->chat->id, BotConstants::Text::SAD_ERROR); - return; - } - - botApi.editMessageText( - response.message, - query->message->chat->id, - query->message->messageId, - "", - "", - nullptr, - response.keyboard - ); -} - void BotHandlers::handleMessage(TgBot::Message::Ptr message) { //TODO: просмотр состояния пользователя return; @@ -90,6 +52,7 @@ void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query) { auto it = userContexts.find(userId); if (it == userContexts.end()) { // TODO: log + sendError(query, BotConstants::Text::AUTH_ERROR); std::cout << "Error: Не нашел пользователя " << userId; return; } @@ -127,7 +90,7 @@ void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query) { // Обработка back по интерфейсу if (data == BotConstants::Callback::NAV_BACK) { if (!popState(ctx)) { - botApi.answerCallbackQuery(query->id, "Некуда возвращаться", true); + sendError(query, BotConstants::Text::SAD_ERROR); return; } auto result = renderCurrent(ctx); @@ -138,7 +101,7 @@ void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query) { // Переходы вперёд (push) auto newStepOpt = computeNextStep(query, current); if (!newStepOpt.has_value()) { - sendError(query->message->chat->id); + sendError(query, BotConstants::Text::SAD_ERROR); return; } @@ -181,4 +144,93 @@ void BotHandlers::increasePayload(int64_t& payload, const UserState curState) { payload = BotConstants::NULL_PAYLOAD; std::cerr << "Error: increasePayload" << std::endl; } +} + +void BotHandlers::editMessage(TgBot::CallbackQuery::Ptr query, HandlerResult response) { + botApi.editMessageText( + response.message, + query->message->chat->id, + query->message->messageId, + "", + "", + nullptr, + response.keyboard + ); +} + +HandlerResult BotHandlers::renderCurrent(const UserContext& ctx) { + const auto& step = ctx.history.back(); + switch (step.state) { + case UserState::MAIN_MENU: + return showMainMenu(); + case UserState::VIEWING_MY_TITLES: + return returnMyTitles(ctx.userId, step.payload); // payload = offset + /* + case UserState::VIEWING_TITLE_PAGE: + return returnTitlePage(step.payload); // payload = titleId + case UserState::VIEWING_REVIEW: + return returnReview(step.payload); // payload = reviewId + case UserState::AWAITING_REVIEW: + return HandlerResult{"Пришлите текст отзыва:", nullptr}; + // ... + */ + default: + return HandlerResult{BotConstants::Text::SAD_ERROR, nullptr}; + } +} + +HandlerResult BotHandlers::showMainMenu() { + auto keyboard = KeyboardFactory::createMainMenu(); + + return HandlerResult{BotConstants::Text::MAIN_MENU, keyboard}; +} + +void BotHandlers::sendError(TgBot::CallbackQuery::Ptr query, const std::string& errText) { + //TODO: посылать сообщение с кнопкой возврата в главное меню + auto keyboard = nullptr; + + editMessage(query, {errText, keyboard}); +} + +std::optional<NavigationStep> BotHandlers::computeNextStep( + const TgBot::CallbackQuery::Ptr& query, + const NavigationStep& current +) { + const std::string& data = query->data; + + switch (current.state) { + case UserState::MAIN_MENU: + if (data == BotConstants::Callback::MY_TITLES) { + return NavigationStep{UserState::VIEWING_MY_TITLES, 0}; + } + break; + /* + case UserState::VIEWING_MY_TITLES: + if (data.starts_with("title_")) { + int64_t titleId = parseId(data); + return NavigationStep{UserState::VIEWING_TITLE_PAGE, titleId}; + } + break; + + case UserState::VIEWING_TITLE_PAGE: + if (data == BotConstants::Callback::ACTION_ADD_REVIEW) { + return NavigationStep{UserState::AWAITING_REVIEW, current.payload}; + } + if (data.starts_with("review_")) { + int64_t reviewId = parseId(data); + return NavigationStep{UserState::VIEWING_REVIEW, reviewId}; + } + break; + + // Добавляйте другие переходы по мере роста функционала + */ + default: + break; + } + return std::nullopt; +} + +void BotHandlers::createInitContext(int64_t chatId) { + NavigationStep init = {UserState::MAIN_MENU, BotConstants::NULL_PAYLOAD}; + userContexts[chatId] = {chatId, {init}}; } \ No newline at end of file