diff --git a/Containerfile b/Containerfile index a7a2dc7..b7fd545 100644 --- a/Containerfile +++ b/Containerfile @@ -44,7 +44,7 @@ RUN cmake \ dtranslatebot RUN cmake --build dtranslatebot-build RUN cmake --install dtranslatebot-build --strip -RUN echo "{\"translator\":{\"type\":\"stub\"}}" | jq > dtranslatebot.json +RUN echo '{"translator":{"type":"stub"}}' | jq > dtranslatebot.json # Create the dtranslatebot Container FROM alpine:3.23 diff --git a/src/core/database.cpp b/src/core/database.cpp index 2911138..1fe3ea7 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -102,6 +102,22 @@ std::vector database::get_guilds() return {}; } +bot::settings::user database::get_user(dpp::snowflake user_id) +{ +#ifndef NDEBUG + std::cerr << "[Debug] database::get_user(dpp::snowflake) have being called." << std::endl; +#endif + return {}; +} + +std::vector database::get_users() +{ +#ifndef NDEBUG + std::cerr << "[Debug] database::get_users() have being called." << std::endl; +#endif + return {}; +} + void database::set_channel_source(dpp::snowflake guild_id, dpp::snowflake channel_id, const std::string &source) { #ifndef NDEBUG @@ -109,6 +125,13 @@ void database::set_channel_source(dpp::snowflake guild_id, dpp::snowflake channe #endif } +void database::set_user_target(dpp::snowflake user_id, const std::string &target) +{ +#ifndef NDEBUG + std::cerr << "[Debug] database::set_user_target(dpp::snowflake, const std::string&) have being called." << std::endl; +#endif +} + bool database::sync() { #ifndef NDEBUG diff --git a/src/core/database.h b/src/core/database.h index 2589f46..e8d64db 100644 --- a/src/core/database.h +++ b/src/core/database.h @@ -48,7 +48,10 @@ namespace bot { virtual std::vector get_channel_targets(dpp::snowflake guild_id, dpp::snowflake channel_id); */ virtual std::vector get_guilds(); + virtual bot::settings::user get_user(dpp::snowflake user_id); + virtual std::vector get_users(); virtual void set_channel_source(dpp::snowflake guild_id, dpp::snowflake channel_id, const std::string &source); + virtual void set_user_target(dpp::snowflake user_id, const std::string &target); virtual bool sync(); }; } diff --git a/src/core/http_request.cpp b/src/core/http_request.cpp index 7d780b4..58d9e0e 100644 --- a/src/core/http_request.cpp +++ b/src/core/http_request.cpp @@ -17,7 +17,6 @@ *****************************************************************************/ #include "http_request.h" -using namespace std::string_literals; http_request::http_request() { instance = curl_easy_init(); @@ -56,7 +55,7 @@ const http_response http_request::post(const std::string &url, const std::string curl_easy_setopt(instance, CURLOPT_URL, url.c_str()); curl_slist *header_slist = nullptr; if (!content_type.empty()) { - curl_slist *new_header_slist = curl_slist_append(header_slist, std::string("Content-Type: "s + content_type).c_str()); + curl_slist *new_header_slist = curl_slist_append(header_slist, std::string("Content-Type: " + content_type).c_str()); if (!new_header_slist) { curl_slist_free_all(header_slist); throw std::bad_alloc(); @@ -88,7 +87,7 @@ const http_response http_request::post(const std::string &url, const std::string } std::string http_request::legacy_url(const std::string &hostname, uint16_t port, const std::string &url, bool tls) { - return (tls ? "https://"s : "http://"s) + hostname + ":"s + std::to_string(port) + (url.empty() ? "/"s : (url.front() != '/' ? "/"s + url : url)); + return (tls ? "https://" : "http://") + hostname + ":" + std::to_string(port) + (url.empty() ? "/" : (url.front() != '/' ? "/" + url : url)); } http_request::~http_request() { diff --git a/src/core/settings.cpp b/src/core/settings.cpp index b00895a..5ad86b6 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -66,7 +66,7 @@ void process_database_channels(std::shared_ptr database } } -void process_database(std::shared_ptr database, std::vector &guilds, std::vector &webhookIds) +void process_database(std::shared_ptr database, std::vector &guilds, std::vector &users, std::vector &webhookIds) { std::cout << "[Launch] Loading database..." << std::endl; const std::vector db_guilds = database->get_guilds(); @@ -86,6 +86,13 @@ void process_database(std::shared_ptr database, std::ve guilds.push_back(std::move(guild)); } } + const std::vector db_users = database->get_users(); + for (auto db_user_id = db_users.begin(); db_user_id != db_users.end(); db_user_id++) { + bot::settings::user user = database->get_user(*db_user_id); + if (user.target.empty()) + user.target = "en"; + users.push_back(std::move(user)); + } } void process_guild_settings(const dpp::json &json, std::vector &guilds, std::vector &webhookIds) @@ -485,6 +492,20 @@ const target* settings::get_target(const channel *channel, const std::string &ta return nullptr; } +user* settings::get_user(dpp::snowflake user_id) { + if (!m_externallyLockedCount) { +#ifndef NDEBUG + std::cerr << "[Debug] settings::get_user(dpp::snowflake) have being called without settings being locked." << std::endl; +#endif + return nullptr; + } + for (auto user = m_users.begin(); user != m_users.end(); user++) { + if (user->id == user_id) + return &*user; + } + return nullptr; +} + const std::vector settings::preferred_languages() const { const std::lock_guard guard(m_mutex); @@ -509,6 +530,19 @@ const std::string settings::token() const return m_token; } +void settings::set_user_target(dpp::snowflake user_id, std::string target) { + const std::lock_guard guard(m_mutex); + for (auto user = m_users.begin(); user != m_users.end(); user++) { + if (user->id == user_id) { + user->target = target; + return; + } + } + + // We will create the user structure when it is not in memory + m_users.push_back({ user_id, target }); +} + bool settings::is_translatebot(dpp::snowflake webhook_id) const { const std::lock_guard guard(m_mutex); @@ -582,7 +616,7 @@ bool settings::parse(const std::string &data, bool initialize) if (json_user != json.end() && json_user->is_object()) process_user_settings(*json_user, m_avatarSize); - process_database(m_database, m_guilds, m_webhookIds); + process_database(m_database, m_guilds, m_users, m_webhookIds); return true; } diff --git a/src/core/settings.h b/src/core/settings.h index f71f812..80dc3ad 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -45,11 +45,15 @@ namespace bot { target* get_target(dpp::snowflake guild_id, dpp::snowflake channel_id, const std::string &target); static target* get_target(channel *channel, const std::string &target); static const target* get_target(const channel *channel, const std::string &target); + user* get_user(dpp::snowflake user_id); const std::vector preferred_languages() const; std::shared_ptr get_database() const; std::shared_ptr get_translator() const; const std::string token() const; + /* set functions */ + void set_user_target(dpp::snowflake user_id, std::string target); + /* is functions */ bool is_translatebot(dpp::snowflake webhook_id) const; @@ -75,6 +79,7 @@ namespace bot { std::vector m_prefLangs; std::shared_ptr m_translator; std::string m_token; + std::vector m_users; std::vector m_webhookIds; }; } diff --git a/src/core/settings_types.h b/src/core/settings_types.h index 96d9017..911acb6 100644 --- a/src/core/settings_types.h +++ b/src/core/settings_types.h @@ -40,6 +40,10 @@ namespace bot { dpp::snowflake id; std::vector channel; }; + struct user { + dpp::snowflake id; + std::string target; + }; struct translator { std::string type; std::string hostname; diff --git a/src/core/webhook_push.cpp b/src/core/webhook_push.cpp index 3d4e567..3f80881 100644 --- a/src/core/webhook_push.cpp +++ b/src/core/webhook_push.cpp @@ -43,15 +43,15 @@ void bot::webhook_push::run(const bot::translated_guild_message &message, dpp::c } else { bot::svmatch match; - if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*(\\.|\\?|\\!|\\。)\\s.*$"s))) { + if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*(\\.|\\?|\\!|\\。)\\s.*$"))) { json_body["content"] = message_v.substr(0, 1334 + match.position(1)); message_v = message_v.substr(1334 + match.position(1)); } - else if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*(\\,)\\s.*$"s))) { + else if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*(\\,)\\s.*$"))) { json_body["content"] = message_v.substr(0, 1334 + match.position(1)); message_v = message_v.substr(1334 + match.position(1)); } - else if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*()\\s.*$"s))) { + else if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*()\\s.*$"))) { json_body["content"] = message_v.substr(0, 1334 + match.position(1)); message_v = message_v.substr(1334 + match.position(1)); } diff --git a/src/database/file/file.cpp b/src/database/file/file.cpp index e7c6a6d..5097426 100644 --- a/src/database/file/file.cpp +++ b/src/database/file/file.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2024 Syping +* Copyright (C) 2024-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -275,9 +275,8 @@ std::vector file::get_channels(dpp::snowflake guild_id) const std::lock_guard guard(m_mutex); for (auto guild = m_channelCache.begin(); guild != m_channelCache.end(); guild++) { - if (guild->id == guild_id) { + if (guild->id == guild_id) return guild->channel; - } } std::vector channels; @@ -334,6 +333,28 @@ std::vector file::get_guilds() return guilds; } +bot::settings::user file::get_user(dpp::snowflake user_id) +{ + const std::lock_guard guard(m_mutex); + + for (auto user = m_userCache.begin(); user != m_userCache.end(); user++) { + if (user->id == user_id) + return *user; + } + + bot::settings::user user; + cache_get_user(user_id, user); + return user; +} + +std::vector file::get_users() +{ + const std::lock_guard guard(m_mutex); + std::vector users; + list_users(users); + return users; +} + void file::set_channel_source(dpp::snowflake guild_id, dpp::snowflake channel_id, const std::string &source) { const std::lock_guard guard(m_mutex); @@ -363,6 +384,20 @@ void file::set_channel_source(dpp::snowflake guild_id, dpp::snowflake channel_id m_dataCache.push_back({ guild_id, { std::move(channel) } }); } +void file::set_user_target(dpp::snowflake user_id, const std::string &target) +{ + const std::lock_guard guard(m_mutex); + + for (auto user = m_userCache.begin(); user != m_userCache.end(); user++) { + if (user->id == user_id) { + user->target = target; + return; + } + } + + m_userCache.push_back({ user_id, target }); +} + bool file::sync() { const std::lock_guard guard(m_mutex); @@ -444,6 +479,35 @@ void file::cache_get_channel(dpp::snowflake channel_id, settings::channel &chann } } +void file::cache_get_user(dpp::snowflake user_id, settings::user &user) +{ + user.id = user_id; + + const std::filesystem::path user_file = m_storagePath / "user" / (std::to_string(user_id) + ".json"); + + if (!std::filesystem::is_regular_file(user_file)) + return; + + std::ifstream ifs(user_file, std::ios::in | std::ios::binary); + if (!ifs.is_open()) + return; + + std::string sdata(std::istreambuf_iterator{ifs}, {}); + ifs.close(); + + try { + const dpp::json json = dpp::json::parse(sdata); + if (json.is_object()) { + auto json_user_target = json.find("target"); + if (json_user_target != json.end()) + user.target = *json_user_target; + } + } + catch (const std::exception &exception) { + std::cerr << "[Exception] " << exception.what() << std::endl; + } +} + void file::cache_guild(dpp::snowflake guild_id, std::vector &channels) { const std::filesystem::path guild_file = m_storagePath / "guild" / (std::to_string(guild_id) + ".json"); @@ -498,6 +562,31 @@ void file::list_guilds(std::vector &guilds) } } + +void file::list_users(std::vector &users) +{ + const std::filesystem::path user_dir = m_storagePath / "user"; + + if (!std::filesystem::is_directory(user_dir)) + return; + + for (const auto &user_file : std::filesystem::directory_iterator(user_dir)) { + const std::filesystem::path &user_file_path = user_file.path(); + if (user_file_path.extension() == ".json") { + const std::string user_filename = user_file_path.stem().generic_string(); + if (std::all_of(user_filename.begin(), user_filename.end(), ::isdigit)) { + try { + dpp::snowflake user_id = std::stoull(user_filename); + users.push_back(user_id); + } + catch (const std::exception &exception) { + std::cerr << "[Exception] " << exception.what() << std::endl; + } + } + } + } +} + void file::sync_cache() { const std::lock_guard guard(m_mutex); @@ -570,4 +659,34 @@ void file::sync_cache() else { std::cerr << "[Error] Storage guild directory can not be created" << std::endl; } + + const std::filesystem::path user_dir = m_storagePath / "user"; + bool user_dir_exists = std::filesystem::is_directory(user_dir); + if (!user_dir_exists) + user_dir_exists = std::filesystem::create_directory(user_dir); + + if (user_dir_exists) { + for (auto user = m_userCache.begin(); user != m_userCache.end();) { + dpp::json user_json = { + {"target"s, user->target} + }; + + const std::filesystem::path user_file = m_storagePath / "user" / (std::to_string(user->id) + ".json"); + std::ofstream ofs(user_file, std::ios::out | std::ios::binary | std::ios::trunc); + if (ofs.is_open()) { + ofs << user_json.dump(); + bool ok = ofs.good(); + ofs.close(); + if (ok) + user = m_userCache.erase(user); + else + user++; + } + else + user++; + } + } + else { + std::cerr << "[Error] Storage user directory can not be created" << std::endl; + } } diff --git a/src/database/file/file.h b/src/database/file/file.h index f34bd4d..daceefd 100644 --- a/src/database/file/file.h +++ b/src/database/file/file.h @@ -48,14 +48,19 @@ namespace bot { std::vector get_channel_targets(dpp::snowflake guild_id, dpp::snowflake channel_id) override; */ std::vector get_guilds() override; + bot::settings::user get_user(dpp::snowflake user_id) override; + std::vector get_users() override; void set_channel_source(dpp::snowflake guild_id, dpp::snowflake channel_id, const std::string &source) override; + void set_user_target(dpp::snowflake user_id, const std::string &target) override; bool sync() override; private: void cache_add_channel(dpp::snowflake guild_id, dpp::snowflake channel_id); void cache_get_channel(dpp::snowflake channel_id, bot::settings::channel &channel); + void cache_get_user(dpp::snowflake user_id, settings::user &user); void cache_guild(dpp::snowflake guild_id, std::vector &channels); void list_guilds(std::vector &guilds); + void list_users(std::vector &users); void sync_cache(); #if defined(__unix__) int fd; @@ -64,6 +69,7 @@ namespace bot { #endif std::vector m_channelCache; std::vector m_dataCache; + std::vector m_userCache; std::mutex m_mutex; std::filesystem::path m_storagePath; };