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/
|
||||||
2
modules/bot/front/.gitignore
vendored
2
modules/bot/front/.gitignore
vendored
|
|
@ -1,4 +1,2 @@
|
||||||
build/
|
build/
|
||||||
out/
|
out/
|
||||||
.vscode
|
|
||||||
api/generated-client
|
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace BotConstants {
|
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 {
|
namespace Button {
|
||||||
const std::string FIND_ANIME = "Найти аниме";
|
const std::string FIND_ANIME = "Найти аниме";
|
||||||
const std::string MY_TITLES = "Мои тайтлы";
|
const std::string MY_TITLES = "Мои тайтлы";
|
||||||
|
|
@ -16,13 +20,14 @@ namespace BotConstants {
|
||||||
const std::string ADD_STATUS = ACTION + "add_status";
|
const std::string ADD_STATUS = ACTION + "add_status";
|
||||||
const std::string STATUS = "status:";
|
const std::string STATUS = "status:";
|
||||||
const std::string WATCHING = STATUS + "watching";
|
const std::string WATCHING = STATUS + "watching";
|
||||||
const std::string SEEN = STATUS + "seen";
|
const std::string SEEN = STATUS + "seen";
|
||||||
const std::string WANT = STATUS + "want";
|
const std::string WANT = STATUS + "want";
|
||||||
const std::string THROWN = STATUS + "thrown";
|
const std::string THROWN = STATUS + "thrown";
|
||||||
const std::string NAVIGATION = "navigation:";
|
const std::string NAVIGATION = "navigation:";
|
||||||
const std::string MY_TITLES = NAVIGATION + "my_titles";
|
const std::string MY_TITLES = NAVIGATION + "my_titles";
|
||||||
const std::string LIST_PREV = NAVIGATION + "prev";
|
const std::string LIST_PREV = NAVIGATION + "prev"; // Пагинация
|
||||||
const std::string LIST_NEXT = NAVIGATION + "next";
|
const std::string LIST_NEXT = NAVIGATION + "next"; // Пагинация
|
||||||
|
const std::string NAV_BACK = NAVIGATION + "back"; // Возврат по стеку состояний
|
||||||
const std::string CHOICE = "choice:";
|
const std::string CHOICE = "choice:";
|
||||||
}
|
}
|
||||||
namespace Text {
|
namespace Text {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include <tgbot/tgbot.h>
|
#include <tgbot/tgbot.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <structs.hpp>
|
#include <structs.hpp>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
/// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения
|
/// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения
|
||||||
struct HandlerResult {
|
struct HandlerResult {
|
||||||
|
|
@ -9,6 +10,29 @@ 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 {
|
||||||
|
std::vector<NavigationStep> history; // Текущее состояние пользователя + история предыдущих состояний
|
||||||
|
};
|
||||||
|
|
||||||
class BotHandlers {
|
class BotHandlers {
|
||||||
public:
|
public:
|
||||||
BotHandlers(TgBot::Api api) : botApi(api) {;}
|
BotHandlers(TgBot::Api api) : botApi(api) {;}
|
||||||
|
|
@ -28,6 +52,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TgBot::Api botApi;
|
TgBot::Api botApi;
|
||||||
|
std::unordered_map<int64_t, UserContext> userContexts;
|
||||||
|
|
||||||
void handleNavigation(TgBot::CallbackQuery::Ptr query);
|
void handleNavigation(TgBot::CallbackQuery::Ptr query);
|
||||||
|
|
||||||
|
|
@ -37,4 +62,25 @@ private:
|
||||||
/// @param userId Идентификатор пользователя
|
/// @param userId Идентификатор пользователя
|
||||||
/// @return HandlerResult
|
/// @return HandlerResult
|
||||||
static HandlerResult returnMyTitles(int64_t userId);
|
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) {
|
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;
|
const std::string& data = query->data;
|
||||||
if (data == BotConstants::Callback::MY_TITLES) {
|
|
||||||
auto [text, kb] = BotHandlers::returnMyTitles(321);
|
//HandlerResult response;
|
||||||
botApi.editMessageText(
|
//UserContext newCtx;
|
||||||
text,
|
|
||||||
query->message->chat->id,
|
auto [response, newCtx] = newStateNavigation(query, ctx);
|
||||||
query->message->messageId,
|
switch (ctx.state) {
|
||||||
"",
|
case UserState::VIEWING_MY_TITLES:
|
||||||
"",
|
response = BotHandlers::returnMyTitles(ctx.cursor);
|
||||||
nullptr,
|
break;
|
||||||
kb
|
case UserState::VIEWING_REVIEW_LIST:
|
||||||
);
|
response = BotHandlers::returnReviewList(ctx.cursor);
|
||||||
}
|
break;
|
||||||
else {
|
default:
|
||||||
botApi.sendMessage(query->message->chat->id, BotConstants::Text::SAD_ERROR, nullptr, nullptr);
|
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) {
|
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);
|
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