Compare commits

...

11 commits

5 changed files with 189 additions and 22 deletions

1
api/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
generated-client/

View file

@ -1,4 +1,2 @@
build/
out/
.vscode
api/generated-client
out/

View file

@ -3,6 +3,10 @@
#include <string>
namespace BotConstants {
const int64_t NULL_PAYLOAD = -1; // Default value для payload
const int64_t DISP_TITLES_NUM = 6; // Количество тайтлов, отображаемых на страничке
const int64_t DISP_REVIEW_NUM = 4; // Количество ревью, отображаемых на страничке
namespace Button {
const std::string FIND_ANIME = "Найти аниме";
const std::string MY_TITLES = "Мои тайтлы";
@ -16,13 +20,14 @@ namespace BotConstants {
const std::string ADD_STATUS = ACTION + "add_status";
const std::string STATUS = "status:";
const std::string WATCHING = STATUS + "watching";
const std::string SEEN = STATUS + "seen";
const std::string WANT = STATUS + "want";
const std::string THROWN = STATUS + "thrown";
const std::string SEEN = STATUS + "seen";
const std::string WANT = STATUS + "want";
const std::string THROWN = STATUS + "thrown";
const std::string NAVIGATION = "navigation:";
const std::string MY_TITLES = NAVIGATION + "my_titles";
const std::string LIST_PREV = NAVIGATION + "prev";
const std::string LIST_NEXT = NAVIGATION + "next";
const std::string LIST_PREV = NAVIGATION + "prev"; // Пагинация
const std::string LIST_NEXT = NAVIGATION + "next"; // Пагинация
const std::string NAV_BACK = NAVIGATION + "back"; // Возврат по стеку состояний
const std::string CHOICE = "choice:";
}
namespace Text {

View file

@ -2,6 +2,7 @@
#include <tgbot/tgbot.h>
#include <string>
#include <structs.hpp>
#include <unordered_map>
/// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения
struct HandlerResult {
@ -9,6 +10,29 @@ struct HandlerResult {
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 {
std::vector<NavigationStep> history; // Текущее состояние пользователя + история предыдущих состояний
};
class BotHandlers {
public:
BotHandlers(TgBot::Api api) : botApi(api) {;}
@ -28,6 +52,7 @@ public:
private:
TgBot::Api botApi;
std::unordered_map<int64_t, UserContext> userContexts;
void handleNavigation(TgBot::CallbackQuery::Ptr query);
@ -37,4 +62,25 @@ private:
/// @param userId Идентификатор пользователя
/// @return HandlerResult
static HandlerResult returnMyTitles(int64_t userId);
/// @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 Уменьшает значение нагрузки с учетом текущего состояния
/// @param payload Изменяемое значение нагрузки
/// @param curState Текущее состояние
void reducePayload(int64_t& payload, const UserState curState);
/// @brief Увеличивает значение нагрузки с учетом текущего состояния
/// @param payload Изменяемое значение нагрузки
/// @param curState Текущее состояние
void increasePayload(int64_t& payload, const UserState curState);
};

View file

@ -32,22 +32,41 @@ HandlerResult BotHandlers::returnMyTitles(int64_t userId) {
}
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;
if (data == BotConstants::Callback::MY_TITLES) {
auto [text, kb] = BotHandlers::returnMyTitles(321);
botApi.editMessageText(
text,
query->message->chat->id,
query->message->messageId,
"",
"",
nullptr,
kb
);
}
else {
botApi.sendMessage(query->message->chat->id, BotConstants::Text::SAD_ERROR, nullptr, nullptr);
//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) {
@ -65,3 +84,101 @@ void BotHandlers::processCallbackImpl(TgBot::CallbackQuery::Ptr query) {
botApi.sendMessage(query->message->chat->id, BotConstants::Text::SAD_ERROR, nullptr, nullptr);
}
}
void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query) {
int64_t userId = query->from->id;
auto it = userContexts.find(userId);
if (it == userContexts.end()) {
// TODO: log
std::cout << "Error: Не нашел пользователя " << userId;
return;
}
UserContext& ctx = it->second;
const auto& current = ctx.history.back(); // текущий экран
const std::string& data = query->data;
// Пагинация (в списках)
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_FOUND_TITLES)) {
int64_t newPayload = current.payload;
if (data == BotConstants::Callback::LIST_PREV && newPayload > 0) {
reducePayload(newPayload, current.state);
} else if (data == BotConstants::Callback::LIST_NEXT) {
increasePayload(newPayload, current.state);
} else {
if (data == BotConstants::Callback::LIST_PREV) {
std::cout << "Error: navigation:prev callback for 1st page" << std::endl;
return;
}
// TODO: log
std::cout << "Error: navigation:prev unknown error" << std::endl;
}
ctx.history.back().payload = newPayload;
auto result = renderCurrent(ctx);
editMessage(query, result);
return;
}
// Обработка back по интерфейсу
if (data == BotConstants::Callback::NAV_BACK) {
if (!popState(ctx)) {
botApi.answerCallbackQuery(query->id, "Некуда возвращаться", true);
return;
}
auto result = renderCurrent(ctx);
editMessage(query, result);
return;
}
// Переходы вперёд (push)
auto newStepOpt = computeNextStep(query, current);
if (!newStepOpt.has_value()) {
sendError(query->message->chat->id);
return;
}
ctx.history.push_back(*newStepOpt);
auto result = renderCurrent(ctx);
editMessage(query, result);
}
void BotHandlers::pushState(UserContext& ctx, UserState newState, int64_t payload) {
ctx.history.push_back({newState, payload});
}
bool BotHandlers::popState(UserContext& ctx) {
if (ctx.history.size() <= 1) return false; // нельзя выйти из MAIN_MENU
ctx.history.pop_back();
return true;
}
void BotHandlers::reducePayload(int64_t& payload, const UserState curState) {
if (curState == UserState::VIEWING_MY_TITLES ||
curState == UserState::VIEWING_FOUND_TITLES) {
payload -= BotConstants::DISP_TITLES_NUM;
} else if (curState == UserState::VIEWING_REVIEW_LIST) {
payload -= BotConstants::DISP_REVIEW_NUM;
} else {
// TODO: log
payload = BotConstants::NULL_PAYLOAD;
std::cerr << "Error: reducePayload" << std::endl;
}
}
void BotHandlers::increasePayload(int64_t& payload, const UserState curState) {
if (curState == UserState::VIEWING_MY_TITLES ||
curState == UserState::VIEWING_FOUND_TITLES) {
payload += BotConstants::DISP_TITLES_NUM;
} else if (curState == UserState::VIEWING_REVIEW_LIST) {
payload += BotConstants::DISP_REVIEW_NUM;
} else {
// TODO: log
payload = BotConstants::NULL_PAYLOAD;
std::cerr << "Error: increasePayload" << std::endl;
}
}