diff options
| author | soler_j <soler_j@etna-alternance.net> | 2025-05-04 01:26:36 +0200 |
|---|---|---|
| committer | soler_j <soler_j@etna-alternance.net> | 2025-05-04 01:26:36 +0200 |
| commit | e82d081f0fc630fe9244ff9d461f91dd1d4dc63b (patch) | |
| tree | 09c49fec114326a982648ccb4ce961f092a44b19 | |
| parent | 23cba7cd6b32b7c5db98aa5a3f9206d4bce07902 (diff) | |
| parent | 502ae25637f103a063c145509c051d1ce6061e4f (diff) | |
Merge branch '1-more-actions-to-handle' of github.com:ketsuna-org/bot-creator-api into 1-more-actions-to-handle
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | .vscode/settings.json | 2 | ||||
| -rw-r--r-- | Dockerfile | 2 | ||||
| -rw-r--r-- | bot/include/http_webhook_server.hpp | 29 | ||||
| -rw-r--r-- | bot/include/utils.hpp | 28 | ||||
| -rw-r--r-- | bot/src/actions/delete.cpp | 144 | ||||
| -rw-r--r-- | bot/src/handle_actions.cpp | 4 | ||||
| -rw-r--r-- | bot/src/http_webhook_server.cpp | 140 | ||||
| -rw-r--r-- | bot/src/main.cpp | 4 | ||||
| -rw-r--r-- | bot/src/utils.cpp | 39 |
10 files changed, 280 insertions, 113 deletions
@@ -41,3 +41,4 @@ devenv.local.nix .pre-commit-config.yaml build discord-bot +.cache/* diff --git a/.vscode/settings.json b/.vscode/settings.json index 42e44f8..798eedd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "cmake.sourceDirectory": "/home/exa/perso/bot-creator-api/bot", + "cmake.sourceDirectory": "/Users/jeremy/Documents/bot-creator-api/bot", "files.associations": { "map": "cpp", "__bit_reference": "cpp", @@ -41,7 +41,7 @@ RUN mkdir build && cd build && \ cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/usr/local && \ - make -j$(nproc) + make -j2 # Étape finale d'exécution FROM ubuntu:24.04 diff --git a/bot/include/http_webhook_server.hpp b/bot/include/http_webhook_server.hpp index a42829f..8ba6841 100644 --- a/bot/include/http_webhook_server.hpp +++ b/bot/include/http_webhook_server.hpp @@ -1,12 +1,18 @@ #pragma once -#include <sys/epoll.h> -#include <netinet/in.h> #include <functional> #include <string> #include <unordered_map> #include <system_error> #include <sstream> +#include <string> +#include <netinet/in.h> + +#ifdef __linux__ + #include <sys/epoll.h> +#elif defined(__APPLE__) + #include <sys/event.h> +#endif class HttpWebhookServer { public: @@ -27,7 +33,8 @@ public: HttpWebhookServer(uint16_t port, Handler handler); ~HttpWebhookServer(); - + void setupEpoll(); + void handleEvents(); void start(); void stop(); @@ -39,17 +46,29 @@ private: }; void setupSocket(); - void setupEpoll(); - void handleEvent(struct epoll_event* event); + void setupEventLoop(); + void handleEvent(int fd, uint32_t events); void handleClient(int fd); void closeClient(int fd); + void registerFdForRead(int fd); + void modifyFdToWrite(int fd); + void sendToClient(int fd); void parseHttpRequest(ClientContext& ctx, HttpRequest& req); void buildHttpResponse(const HttpResponse& res, std::string& output); int server_fd = -1; + int event_fd = -1; int epoll_fd = -1; bool running = false; uint16_t port; Handler request_handler; std::unordered_map<int, ClientContext> clients; + +#ifdef __linux__ + static constexpr int MAX_EVENTS = 64; + struct epoll_event events[MAX_EVENTS]; +#elif defined(__APPLE__) + static constexpr int MAX_EVENTS = 64; + struct kevent events[MAX_EVENTS]; +#endif }; diff --git a/bot/include/utils.hpp b/bot/include/utils.hpp index 820fd01..7008dec 100644 --- a/bot/include/utils.hpp +++ b/bot/include/utils.hpp @@ -1,7 +1,5 @@ // utils.hpp #pragma once -#ifndef UTILS_HPP -#define UTILS_HPP #include <dpp/dpp.h> #include <dpp/nlohmann/json.hpp> @@ -11,6 +9,12 @@ #include <sstream> #include <algorithm> using namespace dpp; + +enum class Lang { + en, + fr, +}; + namespace app { @@ -81,7 +85,25 @@ namespace app */ std::string string_from_json(const nlohmann::json &j); + /** + * @brief Gets the available locale for a given locale string + * + * @param locale The locale string to check + * @return std::string The available locale or "en" if not found + */ + Lang get_available_locale(std::string locale); + + /** + * @brienf translate a string from a locale, optionnal parameters and a default value + * @param str The string to translate to found in an Array of translations + * @param locale The locale to use for translation + * @param array_translations The array of translations + * @param args The optional parameters to replace in the string + * @return std::string The translated string + */ + std::string translate(const std::string &str, const std::string &locale, const std::unordered_map<Lang, std::unordered_map<std::string, std::string>> &array_translations, const std::unordered_map<std::string, std::string> &args = {}); + + } // namespace dpp -#endif // UTILS_HPP // utils.hpp diff --git a/bot/src/actions/delete.cpp b/bot/src/actions/delete.cpp index 58835cf..da74bbf 100644 --- a/bot/src/actions/delete.cpp +++ b/bot/src/actions/delete.cpp @@ -1,107 +1,127 @@ -#include <dpp/dpp.h> +#include "../../include/utils.hpp" -dpp::task<bool> delete_action(const dpp::slashcommand_t &event, const nlohmann::json &action, const std::unordered_map<std::string, std::string> &key_values, dpp::user &user_ptr, dpp::cluster *cluster) -{ - dpp::guild guild_ptr = event.command.get_guild(); - // let's retrieve the member. - dpp::guild_member member_ptr = guild_ptr.members.find(user_ptr.id)->second; - dpp::guild_member bot_member_ptr = guild_ptr.members.find(cluster->me.id)->second; - std::unordered_map<std::string, std::string> error_messages = { + +const std::unordered_map<Lang, std::unordered_map<std::string, std::string>> error_messages_map = { + {Lang::en, { + {"error_no_messages", "No message to delete."}, {"error", "You need to wait a bit before deleting messages."}, {"error_amount", "The amount of messages to delete must be between 1 and 100."}, - {"error_perm_channel", "You do not have permission to delete messages in this channel."}}; + {"error_perm_channel", "You do not have permission to delete messages in this channel."} + }}, + {Lang::fr, { + {"error_no_messages", "Aucun message à supprimer."}, + {"error", "Vous devez attendre un peu avant de supprimer des messages."}, + {"error_amount", "Le nombre de messages à supprimer doit être compris entre 1 et 100."}, + {"error_perm_channel", "Vous n'avez pas la permission de supprimer des messages dans ce canal."} + }} +}; - if (action.contains("error_amount")) - { - error_messages["error_amount"] = action["error_amount"].get<std::string>(); - } +dpp::task<bool> delete_action(const dpp::slashcommand_t &event, const nlohmann::json &action, + const std::unordered_map<std::string, std::string> &key_values, + dpp::user &user_ptr, dpp::cluster *cluster) +{ + // setup locale for gettext + std::string locale = event.command.locale; + const dpp::channel *channel_ptr = &event.command.get_channel(); + const auto &guild_ptr = event.command.get_guild(); - if (action.contains("error_perm_channel")) - { - error_messages["error_perm_channel"] = action["error_perm_channel"].get<std::string>(); - } + const auto member_it = guild_ptr.members.find(user_ptr.id); + const auto *member_ptr = (member_it != guild_ptr.members.end()) ? &member_it->second : nullptr; + + const auto bot_member_it = guild_ptr.members.find(cluster->me.id); + const auto *bot_member_ptr = (bot_member_it != guild_ptr.members.end()) ? &bot_member_it->second : nullptr; - if (action.contains("error")) + const std::unordered_map<std::string, std::string> error_messages = [&action,&locale]() { - error_messages["error"] = action["error"].get<std::string>(); - } - // let's retrieve the current channel - const dpp::channel *channel_ptr = &event.command.get_channel(); - auto user_as_perms = channel_ptr->get_user_permissions(member_ptr).has(dpp::p_manage_messages); - auto bot_as_perms = channel_ptr->get_user_permissions(bot_member_ptr).has(dpp::p_manage_messages); - if (!user_as_perms) + std::unordered_map<std::string, std::string> defaults = { + {"error_no_messages", app::translate("error_no_messages", locale, error_messages_map)}, + {"error", app::translate("error", locale, error_messages_map)}, + {"error_amount", app::translate("error_amount", locale, error_messages_map)}, + {"error_perm_channel", app::translate("error_perm_channel", locale, error_messages_map)}, + }; + if (action.contains("error_amount")) + defaults["error_amount"] = action["error_amount"]; + if (action.contains("error_perm_channel")) + defaults["error_perm_channel"] = action["error_perm_channel"]; + if (action.contains("error")) + defaults["error"] = action["error"]; + return defaults; + }(); + + if (!member_ptr || !bot_member_ptr) { - event.edit_response(error_messages["error_perm_channel"]); + event.edit_response(error_messages.at("error_perm_channel")); co_return false; } - if (!bot_as_perms) + + const bool has_permissions = channel_ptr->get_user_permissions(*member_ptr).has(dpp::p_manage_messages) && channel_ptr->get_user_permissions(*bot_member_ptr).has(dpp::p_manage_messages); + if (!has_permissions) { - event.edit_response(error_messages["error_perm_channel"]); + event.edit_response(error_messages.at("error_perm_channel")); co_return false; } + int amount = 0; if (action.contains("depend_on")) { - std::string depend_on = action["depend_on"]; - auto it = key_values.find(depend_on); - if (it != key_values.end()) + const std::string &depend_on = action["depend_on"]; + if (const auto it = key_values.find(depend_on); it != key_values.end()) { - std::string depend_on_value = it->second; - - // let's convert the depend_on_value to an int - amount = std::stoi(depend_on_value); - if (amount < 0 || amount > 100) + try + { + amount = std::stoi(it->second); + if (amount < 1 || amount > 100) + { + event.edit_response(error_messages.at("error_amount")); + co_return false; + } + } + catch (const std::exception &e) { - event.edit_response(error_messages["error_amount"]); + event.edit_response(error_messages.at("error")); co_return false; } } } + if (amount > 0) { - dpp::confirmation_callback_t callback = co_await cluster->co_messages_get(channel_ptr->id, 0, 0, 0, amount); + const time_t two_weeks_ago = dpp::utility::time_f() - 1209600; + + auto callback = co_await cluster->co_messages_get(channel_ptr->id, 0, 0, 0, amount); if (callback.is_error()) { - printf("Error: %s\n", callback.get_error().message.c_str()); - event.edit_response(error_messages["error"]); + event.edit_response(error_messages.at("error")); co_return false; } - auto messages = callback.get<dpp::message_map>(); + + const auto &messages = callback.get<dpp::message_map>(); if (messages.empty()) { - event.edit_response("No messages to delete."); + event.edit_response(error_messages.at("error_no_messages")); co_return false; } + std::vector<dpp::snowflake> msg_ids; + msg_ids.reserve(messages.size()); - for (const auto &msg : messages) + for (const auto &[id, msg] : messages) { - // let's check if the message is older than 2 weeks - if (msg.second.get_creation_time() < dpp::utility::time_f() - 1209600) + if (msg.get_creation_time() >= two_weeks_ago) { - printf("Message is older than 2 weeks\n"); - continue; - } - else - { - msg_ids.push_back(msg.second.id); + msg_ids.emplace_back(id); } } if (!msg_ids.empty()) { - dpp::confirmation_callback_t result; - if (msg_ids.size() == 1) - { - result = co_await cluster->co_message_delete(msg_ids[0], channel_ptr->id); - } - else - { - result = co_await cluster->co_message_delete_bulk(msg_ids, channel_ptr->id); - } - if (result.is_error()) + const auto delete_result = msg_ids.size() == 1 + ? co_await cluster->co_message_delete(msg_ids.front(), channel_ptr->id) + : co_await cluster->co_message_delete_bulk(msg_ids, channel_ptr->id); + + if (delete_result.is_error()) { - event.edit_response(error_messages["error"]); + event.edit_response(error_messages.at("error")); co_return false; } } diff --git a/bot/src/handle_actions.cpp b/bot/src/handle_actions.cpp index e469ec7..0b3f2ff 100644 --- a/bot/src/handle_actions.cpp +++ b/bot/src/handle_actions.cpp @@ -1,8 +1,6 @@ -#include <dpp/dpp.h> #include "../include/actions/delete.hpp" dpp::task<bool> handle_actions(const dpp::slashcommand_t &event, const nlohmann::json &actions, const std::unordered_map<std::string, std::string> &key_values) { - dpp::cluster *cluster = event.owner; dpp::user user_ptr = event.command.get_issuing_user(); dpp::async thinking = event.co_thinking(false); @@ -36,4 +34,6 @@ dpp::task<bool> handle_actions(const dpp::slashcommand_t &event, const nlohmann: } } } + co_await thinking; + co_return true; } diff --git a/bot/src/http_webhook_server.cpp b/bot/src/http_webhook_server.cpp index a6daa63..fd01a1f 100644 --- a/bot/src/http_webhook_server.cpp +++ b/bot/src/http_webhook_server.cpp @@ -3,6 +3,15 @@ #include <unistd.h> #include <cstring> #include <algorithm> +#include <sstream> +#include <stdexcept> +#include <system_error> + +#ifdef __linux__ +#include <sys/epoll.h> +#elif defined(__APPLE__) +#include <sys/event.h> +#endif HttpWebhookServer::HttpWebhookServer(uint16_t port, Handler handler) : port(port), request_handler(handler) { @@ -17,12 +26,16 @@ HttpWebhookServer::~HttpWebhookServer() { } void HttpWebhookServer::setupSocket() { - server_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); - if (server_fd == -1) throw std::system_error(errno, std::generic_category()); + server_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (server_fd == -1) + throw std::system_error(errno, std::generic_category()); int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + if (::fcntl(server_fd, F_SETFL, O_NONBLOCK) == -1) + throw std::system_error(errno, std::generic_category()); + sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; @@ -36,6 +49,7 @@ void HttpWebhookServer::setupSocket() { } void HttpWebhookServer::setupEpoll() { +#ifdef __linux__ epoll_fd = ::epoll_create1(0); if (epoll_fd == -1) throw std::system_error(errno, std::generic_category()); @@ -46,33 +60,96 @@ void HttpWebhookServer::setupEpoll() { if (::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) throw std::system_error(errno, std::generic_category()); + +#elif defined(__APPLE__) + epoll_fd = ::kqueue(); + if (epoll_fd == -1) + throw std::system_error(errno, std::generic_category()); + + struct kevent ev_set; + EV_SET(&ev_set, server_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr); + if (kevent(epoll_fd, &ev_set, 1, nullptr, 0, nullptr) == -1) + throw std::system_error(errno, std::generic_category()); +#endif +} + +void HttpWebhookServer::registerFdForRead(int fd) { +#ifdef __linux__ + epoll_event event{}; + event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; + event.data.fd = fd; + ::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event); +#elif defined(__APPLE__) + struct kevent ev_set; + EV_SET(&ev_set, fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr); + kevent(epoll_fd, &ev_set, 1, nullptr, 0, nullptr); +#endif +} + +void HttpWebhookServer::modifyFdToWrite(int fd) { +#ifdef __linux__ + epoll_event event{}; + event.events = EPOLLOUT | EPOLLET | EPOLLRDHUP; + event.data.fd = fd; + ::epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event); +#elif defined(__APPLE__) + struct kevent ev_set[2]; + EV_SET(&ev_set[0], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + EV_SET(&ev_set[1], fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, nullptr); + kevent(epoll_fd, ev_set, 2, nullptr, 0, nullptr); +#endif } void HttpWebhookServer::start() { running = true; + +#ifdef __linux__ epoll_event events[64]; +#elif defined(__APPLE__) + struct kevent events[64]; +#endif while (running) { +#ifdef __linux__ int nfds = ::epoll_wait(epoll_fd, events, 64, -1); +#elif defined(__APPLE__) + int nfds = ::kevent(epoll_fd, nullptr, 0, events, 64, nullptr); +#endif if (nfds == -1 && errno != EINTR) throw std::system_error(errno, std::generic_category()); for (int i = 0; i < nfds; ++i) { - if (events[i].data.fd == server_fd) { +#ifdef __linux__ + int fd = events[i].data.fd; + bool isWritable = events[i].events & EPOLLOUT; +#elif defined(__APPLE__) + int fd = static_cast<int>(events[i].ident); + bool isWritable = events[i].filter == EVFILT_WRITE; +#endif + + if (fd == server_fd) { while (true) { sockaddr_in client_addr{}; socklen_t client_len = sizeof(client_addr); + +#ifdef __linux__ int client_fd = ::accept4(server_fd, (sockaddr*)&client_addr, &client_len, SOCK_NONBLOCK); +#elif defined(__APPLE__) + int client_fd = ::accept(server_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != -1) { + int flags = fcntl(client_fd, F_GETFL, 0); + fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); + } +#endif if (client_fd == -1) break; - epoll_event event{}; - event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; - event.data.fd = client_fd; - ::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event); clients[client_fd] = ClientContext{}; + registerFdForRead(client_fd); } + } else if (isWritable) { + sendToClient(fd); } else { - handleClient(events[i].data.fd); + handleClient(fd); } } } @@ -83,11 +160,35 @@ void HttpWebhookServer::stop() { } void HttpWebhookServer::closeClient(int fd) { +#ifdef __linux__ ::epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, nullptr); +#elif defined(__APPLE__) + struct kevent ev_del[2]; + EV_SET(&ev_del[0], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + EV_SET(&ev_del[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + kevent(epoll_fd, ev_del, 2, nullptr, 0, nullptr); +#endif ::close(fd); clients.erase(fd); } +void HttpWebhookServer::sendToClient(int fd) { + if (!clients.count(fd)) return; + + auto& ctx = clients[fd]; + if (!ctx.output_buffer.empty()) { + ssize_t sent = ::send(fd, + ctx.output_buffer.data() + ctx.bytes_written, + ctx.output_buffer.size() - ctx.bytes_written, + MSG_DONTWAIT); + + if (sent > 0) ctx.bytes_written += sent; + if (ctx.bytes_written == ctx.output_buffer.size()) { + closeClient(fd); + } + } +} + void HttpWebhookServer::handleClient(int fd) { char buffer[4096]; ssize_t count = ::recv(fd, buffer, sizeof(buffer), MSG_DONTWAIT); @@ -103,31 +204,12 @@ void HttpWebhookServer::handleClient(int fd) { HttpResponse res = request_handler(req); buildHttpResponse(res, ctx.output_buffer); - epoll_event event{}; - event.events = EPOLLOUT | EPOLLET | EPOLLRDHUP; - event.data.fd = fd; - ::epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event); + modifyFdToWrite(fd); } - } - else if (count == 0 || (count == -1 && errno != EAGAIN)) { + } else if (count == 0 || (count == -1 && errno != EAGAIN)) { closeClient(fd); return; } - - if (!clients.count(fd)) return; // Client déjà fermé plus haut - - auto& ctx = clients[fd]; - if (!ctx.output_buffer.empty()) { - ssize_t sent = ::send(fd, - ctx.output_buffer.data() + ctx.bytes_written, - ctx.output_buffer.size() - ctx.bytes_written, - MSG_DONTWAIT); - - if (sent > 0) ctx.bytes_written += sent; - if (ctx.bytes_written == ctx.output_buffer.size()) { - closeClient(fd); - } - } } void HttpWebhookServer::parseHttpRequest(ClientContext& ctx, HttpRequest& req) { diff --git a/bot/src/main.cpp b/bot/src/main.cpp index 42e84bd..1edac34 100644 --- a/bot/src/main.cpp +++ b/bot/src/main.cpp @@ -1,5 +1,3 @@ -#include <dpp/dpp.h> -#include <string> #include "../include/utils.hpp" #include "../include/http_webhook_server.hpp" #include "../include/handle_actions.hpp" @@ -20,7 +18,7 @@ dpp::activity_type activity_type_from_string(const std::string& type) { } else if (type == "competing") { return dpp::activity_type::at_competing; } else { - throw std::invalid_argument("Invalid activity type"); + return dpp::activity_type::at_game; // Default to "playing" if the type is unknown } } diff --git a/bot/src/utils.cpp b/bot/src/utils.cpp index 349f584..a2ce928 100644 --- a/bot/src/utils.cpp +++ b/bot/src/utils.cpp @@ -1,10 +1,4 @@ -#include <dpp/dpp.h> -#include <dpp/nlohmann/json.hpp> -#include <map> -#include <string> -#include <regex> -#include <sstream> -#include <algorithm> +#include "../include/utils.hpp" using namespace dpp; namespace app @@ -212,4 +206,35 @@ namespace app } return str; } + + Lang get_available_locale(std::string locale) + { + std::transform(locale.begin(), locale.end(), locale.begin(), ::tolower); + if (locale == "fr" || locale == "fr-fr") + return Lang::fr; + else if (locale == "en" || locale == "en-us") + return Lang::en; + else + return Lang::en; // Default to English if no match is found + } + + std::string translate(const std::string &str, const std::string &locale, const std::unordered_map<Lang, std::unordered_map<std::string, std::string>> &array_translations, const std::unordered_map<std::string, std::string> &args) + { + Lang lang = get_available_locale(locale); + auto it = array_translations.find(lang); + if (it != array_translations.end()) + { + auto it2 = it->second.find(str); + if (it2 != it->second.end()) + { + std::string translation = it2->second; + for (const auto &arg : args) + { + translation = update_string(translation, args); + } + return translation; + } + } + return str; // Return the original string if no translation is found + } } |
