Compare commits
11 commits
3d8abc3f0c
...
0fdf577612
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fdf577612 | ||
|
|
c815e96f4c | ||
|
|
d69f5fcddf | ||
|
|
b368ecc43b | ||
|
|
a8dd448c95 | ||
|
|
e09b6658b2 | ||
|
|
28a7d9e691 | ||
|
|
12648e1a8f | ||
|
|
7efd7bb6b0 | ||
|
|
bd309d38c6 | ||
|
|
167e2323be |
5 changed files with 189 additions and 22 deletions
1
api/.gitignore
vendored
Normal file
1
api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
generated-client/
|
||||
4
modules/bot/front/.gitignore
vendored
4
modules/bot/front/.gitignore
vendored
|
|
@ -1,4 +1,2 @@
|
|||
build/
|
||||
out/
|
||||
.vscode
|
||||
api/generated-client
|
||||
out/
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue