feat(tgbot-back): start to develop back
Implemented fetchUserTitlesAsync func and embedded it in the code of the front in the trial mode. It needs to be restructured
This commit is contained in:
parent
4ca8b19adb
commit
847aec7bdd
9 changed files with 190 additions and 13 deletions
31
modules/bot/back/include/BotToServer.hpp
Normal file
31
modules/bot/back/include/BotToServer.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
#include "CppRestOpenAPIClient/ApiClient.h"
|
||||||
|
#include "CppRestOpenAPIClient/ApiConfiguration.h"
|
||||||
|
#include "CppRestOpenAPIClient/api/DefaultApi.h"
|
||||||
|
#include "CppRestOpenAPIClient/model/User.h"
|
||||||
|
#include "CppRestOpenAPIClient/model/GetUserTitles_200_response.h"
|
||||||
|
#include "CppRestOpenAPIClient/model/UserTitle.h"
|
||||||
|
#include "CppRestOpenAPIClient/model/Title.h"
|
||||||
|
#include "constants.hpp"
|
||||||
|
#include "structs.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <cpprest/asyncrt_utils.h>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
using namespace org::openapitools::client::api;
|
||||||
|
|
||||||
|
class BotToServer {
|
||||||
|
public:
|
||||||
|
BotToServer();
|
||||||
|
|
||||||
|
// Асинхронный метод: получить список тайтлов пользователя
|
||||||
|
pplx::task<std::vector<BotStructs::Title>> fetchUserTitlesAsync(const std::string& userId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<org::openapitools::client::api::ApiConfiguration> apiconfiguration;
|
||||||
|
std::shared_ptr<org::openapitools::client::api::ApiClient> apiclient;
|
||||||
|
std::shared_ptr<org::openapitools::client::api::DefaultApi> api;
|
||||||
|
};
|
||||||
93
modules/bot/back/src/BotToServer.cpp
Normal file
93
modules/bot/back/src/BotToServer.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
#include "BotToServer.hpp"
|
||||||
|
|
||||||
|
BotToServer::BotToServer() {
|
||||||
|
apiconfiguration = std::make_shared<ApiConfiguration>();
|
||||||
|
const char* envUrl = getenv("NYANIMEDBBASEURL");
|
||||||
|
if (!envUrl) {
|
||||||
|
std::runtime_error("Environment variable NYANIMEDBBASEURL is not set");
|
||||||
|
}
|
||||||
|
apiconfiguration->setBaseUrl(utility::conversions::to_string_t(envUrl));
|
||||||
|
apiconfiguration->setUserAgent(utility::conversions::to_string_t("OpenAPI Client"));
|
||||||
|
apiclient = std::make_shared<ApiClient>(apiconfiguration);
|
||||||
|
api = std::make_shared<DefaultApi>(apiclient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательная функция: преобразует UserTitle → BotStructs::Title
|
||||||
|
static BotStructs::Title mapUserTitleToBotTitle(
|
||||||
|
const std::shared_ptr<org::openapitools::client::model::UserTitle>& userTitle
|
||||||
|
) {
|
||||||
|
if (!userTitle || !userTitle->titleIsSet()) {
|
||||||
|
return BotStructs::Title{0, "Invalid", "", -1};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto apiTitle = userTitle->getTitle();
|
||||||
|
if (!apiTitle) {
|
||||||
|
return BotStructs::Title{0, "No Title", "", -1};
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t id = apiTitle->getId();
|
||||||
|
|
||||||
|
std::string name = "Untitled";
|
||||||
|
auto titleNames = apiTitle->getTitleNames();
|
||||||
|
utility::string_t ru = U("ru");
|
||||||
|
|
||||||
|
if (titleNames.find(ru) != titleNames.end() && !titleNames.at(ru).empty()) {
|
||||||
|
name = utility::conversions::to_utf8string(titleNames.at(ru).front());
|
||||||
|
} else if (!titleNames.empty()) {
|
||||||
|
const auto& firstLang = *titleNames.begin();
|
||||||
|
if (!firstLang.second.empty()) {
|
||||||
|
name = utility::conversions::to_utf8string(firstLang.second.front());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string description = ""; // описание пока не поддерживается в OpenAPI-модели
|
||||||
|
return BotStructs::Title{id, name, description, -1};
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<std::vector<BotStructs::Title>> BotToServer::fetchUserTitlesAsync(const std::string& userId) {
|
||||||
|
utility::string_t userIdW = utility::conversions::to_string_t(userId);
|
||||||
|
int32_t limit = static_cast<int32_t>(BotConstants::DISP_TITLES_NUM);
|
||||||
|
|
||||||
|
auto responseTask = api->getUserTitles(
|
||||||
|
userIdW,
|
||||||
|
boost::none, // cursor
|
||||||
|
boost::none, // sort
|
||||||
|
boost::none, // sortForward
|
||||||
|
boost::none, // word
|
||||||
|
boost::none, // status
|
||||||
|
boost::none, // watchStatus
|
||||||
|
boost::none, // rating
|
||||||
|
boost::none, // myRate
|
||||||
|
boost::none, // releaseYear
|
||||||
|
boost::none, // releaseSeason
|
||||||
|
limit,
|
||||||
|
boost::none // fields
|
||||||
|
);
|
||||||
|
|
||||||
|
return responseTask.then([=](pplx::task<std::shared_ptr<org::openapitools::client::model::GetUserTitles_200_response>> task) {
|
||||||
|
try {
|
||||||
|
auto response = task.get();
|
||||||
|
if (!response) {
|
||||||
|
throw std::runtime_error("Null response from getUserTitles");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& userTitles = response->getData();
|
||||||
|
std::vector<BotStructs::Title> result;
|
||||||
|
result.reserve(userTitles.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < userTitles.size(); ++i) {
|
||||||
|
BotStructs::Title botTitle = mapUserTitleToBotTitle(userTitles[i]);
|
||||||
|
botTitle.num = static_cast<int64_t>(i); // 0-based индекс
|
||||||
|
result.push_back(botTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (const web::http::http_exception& e) {
|
||||||
|
std::cerr << "HTTP error in fetchUserTitlesAsync: " << e.what() << std::endl;
|
||||||
|
throw;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error in fetchUserTitlesAsync: " << e.what() << std::endl;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
cmake_minimum_required(VERSION 3.10.2)
|
cmake_minimum_required(VERSION 3.10.2)
|
||||||
project(AnimeBot)
|
project(AnimeBot)
|
||||||
|
|
||||||
file(GLOB SOURCES "src/*.cpp")
|
set(SOURCES "")
|
||||||
|
file(GLOB_RECURSE SRC_FRONT "src/*.cpp")
|
||||||
|
list(APPEND SOURCES ${SRC_FRONT})
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SRC_BACK "../back/src/*.cpp")
|
||||||
|
list(APPEND SOURCES ${SRC_BACK})
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SRC_API "../generated-client/src/*.cpp")
|
||||||
|
list(APPEND SOURCES ${SRC_API})
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||||
|
|
@ -12,8 +21,11 @@ find_package(Threads REQUIRED)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
find_package(Boost COMPONENTS system REQUIRED)
|
find_package(Boost COMPONENTS system REQUIRED)
|
||||||
find_package(CURL)
|
find_package(CURL)
|
||||||
|
find_library(CPPREST_LIB cpprest REQUIRED)
|
||||||
include_directories(/usr/local/include ${OPENSSL_INCLUDE_DIR} ${Boost_INCLUDE_DIR})
|
include_directories(/usr/local/include ${OPENSSL_INCLUDE_DIR} ${Boost_INCLUDE_DIR})
|
||||||
include_directories(include/)
|
include_directories(include/)
|
||||||
|
include_directories(../back/include)
|
||||||
|
include_directories(../generated-client/include)
|
||||||
if (CURL_FOUND)
|
if (CURL_FOUND)
|
||||||
include_directories(${CURL_INCLUDE_DIRS})
|
include_directories(${CURL_INCLUDE_DIRS})
|
||||||
add_definitions(-DHAVE_CURL)
|
add_definitions(-DHAVE_CURL)
|
||||||
|
|
@ -21,4 +33,4 @@ endif()
|
||||||
|
|
||||||
add_executable(AnimeBot ${SOURCES})
|
add_executable(AnimeBot ${SOURCES})
|
||||||
|
|
||||||
target_link_libraries(AnimeBot /usr/local/lib/libTgBot.a ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${CURL_LIBRARIES})
|
target_link_libraries(AnimeBot /usr/local/lib/libTgBot.a ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${CURL_LIBRARIES} ${CPPREST_LIB})
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ public:
|
||||||
static TgBot::InlineKeyboardMarkup::Ptr createMainMenu();
|
static TgBot::InlineKeyboardMarkup::Ptr createMainMenu();
|
||||||
|
|
||||||
/// Create keyboard for My_Titles
|
/// Create keyboard for My_Titles
|
||||||
static TgBot::InlineKeyboardMarkup::Ptr createMyTitles(std::vector<Title> titles);
|
static TgBot::InlineKeyboardMarkup::Ptr createMyTitles(std::vector<BotStructs::Title> titles);
|
||||||
|
|
||||||
/// Create keyboard for sendError
|
/// Create keyboard for sendError
|
||||||
static TgBot::InlineKeyboardMarkup::Ptr createError(const std::string& errorCallback);
|
static TgBot::InlineKeyboardMarkup::Ptr createError(const std::string& errorCallback);
|
||||||
|
|
|
||||||
|
|
@ -39,5 +39,6 @@ namespace BotConstants {
|
||||||
const std::string MAIN_MENU = "Вас приветствует nyanimedb бот:)\nЧего будем делать?";
|
const std::string MAIN_MENU = "Вас приветствует nyanimedb бот:)\nЧего будем делать?";
|
||||||
const std::string SAD_ERROR = "У нас что-то случилось:(\nМы обязательно скоро исправимся";
|
const std::string SAD_ERROR = "У нас что-то случилось:(\nМы обязательно скоро исправимся";
|
||||||
const std::string AUTH_ERROR = "Проблемы с авторизацией, попробуйте авторизоваться повторно";
|
const std::string AUTH_ERROR = "Проблемы с авторизацией, попробуйте авторизоваться повторно";
|
||||||
|
const std::string SERVER_ERROR = "Не удалось загрузить данные. Попробуйте позже.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <structs.hpp>
|
#include <structs.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include "BotToServer.hpp"
|
||||||
|
|
||||||
/// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения
|
/// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения
|
||||||
struct HandlerResult {
|
struct HandlerResult {
|
||||||
|
|
@ -58,6 +59,7 @@ public:
|
||||||
private:
|
private:
|
||||||
TgBot::Api botApi;
|
TgBot::Api botApi;
|
||||||
std::unordered_map<int64_t, UserContext> userContexts;
|
std::unordered_map<int64_t, UserContext> userContexts;
|
||||||
|
BotToServer server_;
|
||||||
|
|
||||||
void handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext& ctx);
|
void handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext& ctx);
|
||||||
|
|
||||||
|
|
@ -104,7 +106,7 @@ private:
|
||||||
/// @brief Отрисовка текущего экрана (соотв. контексту)
|
/// @brief Отрисовка текущего экрана (соотв. контексту)
|
||||||
/// @param ctx - текущий контекст
|
/// @param ctx - текущий контекст
|
||||||
/// @return HandlerResult для нового состояния сообщения
|
/// @return HandlerResult для нового состояния сообщения
|
||||||
HandlerResult renderCurrent(const UserContext& ctx);
|
HandlerResult renderCurrent(TgBot::CallbackQuery::Ptr query, const UserContext& ctx);
|
||||||
|
|
||||||
/// @brief Логика переходов между контекстами (навигация на следующий шаг)
|
/// @brief Логика переходов между контекстами (навигация на следующий шаг)
|
||||||
/// @param query - запрос
|
/// @param query - запрос
|
||||||
|
|
@ -120,4 +122,7 @@ private:
|
||||||
/// @brief Посылает интерфейс обработки ошибки на callback запрос
|
/// @brief Посылает интерфейс обработки ошибки на callback запрос
|
||||||
/// @param query запрос
|
/// @param query запрос
|
||||||
void sendError(TgBot::CallbackQuery::Ptr query, const std::string& errText);
|
void sendError(TgBot::CallbackQuery::Ptr query, const std::string& errText);
|
||||||
|
|
||||||
|
// Форматирование для отображения в сообщении
|
||||||
|
std::string formatTitlesList(const std::vector<BotStructs::Title>& titles);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
namespace BotStructs {
|
||||||
struct Title {
|
struct Title {
|
||||||
int64_t id;
|
int64_t id;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string description;
|
std::string description;
|
||||||
int64_t num;
|
int64_t num;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMainMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Переписать с учетом констант на количество отображаемых тайтлов и нового callback'a
|
// TODO: Переписать с учетом констант на количество отображаемых тайтлов и нового callback'a
|
||||||
TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMyTitles(std::vector<Title> titles) {
|
TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMyTitles(std::vector<BotStructs::Title> titles) {
|
||||||
auto keyboard = std::make_shared<TgBot::InlineKeyboardMarkup>();
|
auto keyboard = std::make_shared<TgBot::InlineKeyboardMarkup>();
|
||||||
std::vector<TgBot::InlineKeyboardButton::Ptr> row;
|
std::vector<TgBot::InlineKeyboardButton::Ptr> row;
|
||||||
std::vector<std::vector<TgBot::InlineKeyboardButton::Ptr>> layout;
|
std::vector<std::vector<TgBot::InlineKeyboardButton::Ptr>> layout;
|
||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
for(Title& title : titles) {
|
for(BotStructs::Title& title : titles) {
|
||||||
if(counter >= 6) {
|
if(counter >= 6) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ void BotHandlers::handleCallback(TgBot::CallbackQuery::Ptr query) {
|
||||||
|
|
||||||
HandlerResult BotHandlers::returnMyTitles(int64_t userId, int64_t payload) {
|
HandlerResult BotHandlers::returnMyTitles(int64_t userId, int64_t payload) {
|
||||||
// Здесь должен происходить запрос на сервер
|
// Здесь должен происходить запрос на сервер
|
||||||
std::vector<Title> titles = {{123, "Школа мертвяков", "", 1}, {321, "KissXsis", "", 2}};
|
std::vector<BotStructs::Title> titles = {{123, "Школа мертвяков", "", 1}, {321, "KissXsis", "", 2}};
|
||||||
|
|
||||||
struct HandlerResult result;
|
struct HandlerResult result;
|
||||||
result.keyboard = KeyboardFactory::createMyTitles(titles);
|
result.keyboard = KeyboardFactory::createMyTitles(titles);
|
||||||
|
|
@ -85,7 +85,8 @@ void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext&
|
||||||
|
|
||||||
ctx.history.back().payload = newPayload;
|
ctx.history.back().payload = newPayload;
|
||||||
|
|
||||||
auto result = renderCurrent(ctx);
|
auto result = renderCurrent(query, ctx);
|
||||||
|
if(result.message == "meow") return; // TODO: убрать
|
||||||
editMessage(query, result);
|
editMessage(query, result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +97,8 @@ void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext&
|
||||||
sendError(query, BotConstants::Text::SAD_ERROR);
|
sendError(query, BotConstants::Text::SAD_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto result = renderCurrent(ctx);
|
auto result = renderCurrent(query, ctx);
|
||||||
|
if(result.message == "meow") return; // TODO: убрать
|
||||||
editMessage(query, result);
|
editMessage(query, result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +111,8 @@ void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext&
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.history.push_back(*newStepOpt);
|
ctx.history.push_back(*newStepOpt);
|
||||||
auto result = renderCurrent(ctx);
|
auto result = renderCurrent(query, ctx);
|
||||||
|
if(result.message == "meow") return; // TODO: убрать
|
||||||
editMessage(query, result);
|
editMessage(query, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,13 +164,30 @@ void BotHandlers::editMessage(TgBot::CallbackQuery::Ptr query, HandlerResult res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
HandlerResult BotHandlers::renderCurrent(const UserContext& ctx) {
|
HandlerResult BotHandlers::renderCurrent(TgBot::CallbackQuery::Ptr query, const UserContext& ctx) {
|
||||||
const auto& step = ctx.history.back();
|
const auto& step = ctx.history.back();
|
||||||
|
int64_t userId = query->from->id;
|
||||||
switch (step.state) {
|
switch (step.state) {
|
||||||
case UserState::MAIN_MENU:
|
case UserState::MAIN_MENU:
|
||||||
return showMainMenu();
|
return showMainMenu();
|
||||||
case UserState::VIEWING_MY_TITLES:
|
case UserState::VIEWING_MY_TITLES:
|
||||||
return returnMyTitles(ctx.userId, step.payload); // payload = offset
|
server_.fetchUserTitlesAsync(std::to_string(2)) // ALARM: тестовое значение вместо userId
|
||||||
|
.then([this, query](pplx::task<std::vector<BotStructs::Title>> t) {
|
||||||
|
try {
|
||||||
|
auto titles = t.get();
|
||||||
|
|
||||||
|
std::string message = formatTitlesList(titles);
|
||||||
|
auto keyboard = KeyboardFactory::createMyTitles(titles);
|
||||||
|
|
||||||
|
editMessage(query, {message, keyboard});
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
sendError(query, BotConstants::Text::SERVER_ERROR);
|
||||||
|
// Логирование ошибки (например, в cerr)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {"meow", nullptr};
|
||||||
/*
|
/*
|
||||||
case UserState::VIEWING_TITLE_PAGE:
|
case UserState::VIEWING_TITLE_PAGE:
|
||||||
return returnTitlePage(step.payload); // payload = titleId
|
return returnTitlePage(step.payload); // payload = titleId
|
||||||
|
|
@ -257,3 +277,16 @@ void BotHandlers::handleError(TgBot::CallbackQuery::Ptr query, UserContext& ctx)
|
||||||
editMessage(query, result);
|
editMessage(query, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string BotHandlers::formatTitlesList(const std::vector<BotStructs::Title>& titles) {
|
||||||
|
if (titles.empty()) {
|
||||||
|
return "У вас пока нет тайтлов.";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string msg;
|
||||||
|
for (size_t i = 0; i < titles.size(); ++i) {
|
||||||
|
// num — 0-based, но в сообщении показываем 1-based
|
||||||
|
msg += std::to_string(i + 1) + ". " + titles[i].name + "\n";
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue