diff options
| author | soler_j <soler_j@etna-alternance.net> | 2025-04-29 03:07:48 +0200 |
|---|---|---|
| committer | soler_j <soler_j@etna-alternance.net> | 2025-04-29 03:07:48 +0200 |
| commit | 2a58d43c3b820eb8ffec01bb51905146a7278533 (patch) | |
| tree | d05b0b815aee8520ae1375b69a503511f7b260fa | |
| parent | 0c53f3e452830b300c598c9370946ae54f443a78 (diff) | |
Ajout de la prise en charge de ZeroMQ dans le bot, mise à jour des dépendances et réorganisation du Dockerfile. Suppression de l'implémentation de serveur Unix obsolète et amélioration de la gestion des messages.
| -rw-r--r-- | .vscode/settings.json | 10 | ||||
| -rw-r--r-- | Dockerfile | 37 | ||||
| -rw-r--r-- | app/cmd/main.go | 55 | ||||
| -rw-r--r-- | app/go.mod | 5 | ||||
| -rw-r--r-- | app/go.sum | 4 | ||||
| -rw-r--r-- | app/internal/create_bot.go | 74 | ||||
| -rw-r--r-- | bot/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | bot/include/server.cpp | 183 | ||||
| -rw-r--r-- | bot/src/main.cpp | 90 | ||||
| -rw-r--r-- | devenv.nix | 7 |
10 files changed, 181 insertions, 285 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a84036..42e44f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -86,6 +86,14 @@ "type_traits": "cpp", "utility": "cpp", "format": "cpp", - "stop_token": "cpp" + "stop_token": "cpp", + "codecvt": "cpp", + "numeric": "cpp", + "regex": "cpp", + "numbers": "cpp", + "ranges": "cpp", + "semaphore": "cpp", + "cinttypes": "cpp", + "unordered_set": "cpp" } } @@ -1,11 +1,20 @@ # Étape de compilation pour le programme Go -FROM golang:1.23-alpine AS go-builder +FROM golang:1.23 AS go-builder + WORKDIR /app + COPY app/ . -RUN CGO_ENABLED=0 go build -o /api/app ./cmd/main.go + +RUN apt-get update && apt-get install -y \ + pkg-config \ + libczmq-dev \ + libzmq3-dev \ + libsodium-dev + +RUN CGO_ENABLED=1 go build -o /api/app ./cmd/main.go # Étape de compilation pour le programme C++ avec DPP -FROM ubuntu:24.04 AS cpp-builder +FROM ubuntu:24.10 AS cpp-builder # Install build dependencies RUN apt-get update && apt-get install -y \ @@ -16,7 +25,10 @@ RUN apt-get update && apt-get install -y \ zlib1g-dev \ libopus-dev \ clang \ - pkg-config + pkg-config \ + libczmq-dev \ + libzmq3-dev \ + libsodium-dev # Clone DPP RUN git clone https://github.com/brainboxdotcc/DPP.git /dpp && \ @@ -27,8 +39,7 @@ RUN git clone https://github.com/brainboxdotcc/DPP.git /dpp && \ # Build DPP (shared) RUN mkdir -p /dpp/build && \ cd /dpp/build && \ - cmake .. \ - -DDPP_BUILD_TEST=OFF && \ + cmake .. -DDPP_BUILD_TEST=OFF && \ make -j$(nproc) && \ make install @@ -43,11 +54,19 @@ RUN mkdir build && cd build && \ make -j$(nproc) # Étape finale d'exécution -FROM ubuntu:24.04 +FROM ubuntu:24.10 + WORKDIR /app # Install runtime deps -RUN apt-get update && apt-get install -y libssl3 zlib1g libopus0 && apt-get clean +RUN apt-get update && apt-get install -y \ + libssl3 \ + zlib1g \ + libopus0 \ + libsodium23 \ + libzmq5 \ + libczmq5 \ + && apt-get clean # Copie des binaires COPY --from=go-builder /api ./api @@ -57,7 +76,7 @@ COPY --from=cpp-builder /usr/local/lib/ /usr/local/lib/ # Make sure executables are runnable RUN chmod +x ./api/app ./bot/build/discord-bot -# Pour être sûr que libdpp.so soit trouvée +# Pour être sûr que libdpp.so et libzmq.so soient trouvées ENV LD_LIBRARY_PATH=/usr/local/lib ENTRYPOINT ["./api/app"] diff --git a/app/cmd/main.go b/app/cmd/main.go index 14980b7..87212ef 100644 --- a/app/cmd/main.go +++ b/app/cmd/main.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "fmt" "log" "net/http" "os" @@ -10,6 +9,7 @@ import ( "syscall" "github.com/ketsuna-org/bot-creator-api/internal" + zmq "github.com/pebbe/zmq4" ) func init() { @@ -23,18 +23,33 @@ func main() { mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, World!")) }) - botId := "xxxxx" - botToken := "xxxxx" + botToken := "XXXXXXXXXXXX" // Replace with your bot token + + ctx, err := zmq.NewContext() + if err != nil { + log.Fatalf("[SERVER] Failed to create context: %v", err) + } + defer ctx.Term() + + dealer, err := ctx.NewSocket(zmq.REP) + if err != nil { + log.Fatalf("[SERVER] Failed to create dealer: %v", err) + } + defer dealer.Close() + + err = dealer.Bind("tcp://*:5555") + if err != nil { + log.Fatalf("[SERVER] Failed to bind dealer: %v", err) + } bot := &internal.Bot{ - BotID: botId, BotToken: botToken, } - conn, err := internal.Start(bot) + + bot, err = internal.Start(bot, dealer) if err != nil { - log.Fatalf("Error starting bot: %v", err) + log.Fatalf("[SERVER] Error starting bot: %v", err) } - defer conn.Close() // Handle the bot connection data, err := json.Marshal(map[string]interface{}{ "command": "update", @@ -45,10 +60,22 @@ func main() { }, }) if err != nil { - log.Fatalf("Error marshaling JSON: %v", err) + log.Fatalf("[SERVER] Error marshaling JSON: %v", err) } - conn.Write(data) + go bot.SendMessage(string(data)) + dataX, err := json.Marshal(map[string]interface{}{ + "command": "update", + "data": map[string]interface{}{ + "ping": map[string]string{ + "response": "pong ((userName)) avec une modif !", + }, + }, + }) + if err != nil { + log.Fatalf("[SERVER] Error marshaling JSON: %v", err) + } + go bot.SendMessage(string(dataX)) // Handle if signal is received signals := make(chan os.Signal, 1) @@ -60,18 +87,12 @@ func main() { // let's kill the bot if bot.Cmd != nil { if err := bot.Cmd.Process.Kill(); err != nil { - log.Printf("Error killing bot process: %v", err) + log.Printf("[SERVER] Error killing bot process: %v", err) } else { - log.Printf("Bot process killed successfully") + log.Printf("[SERVER] Bot process killed successfully") } } // let's remove the socket - socketPath := fmt.Sprintf("/tmp/%s.sock", bot.BotID) - if err := os.RemoveAll(socketPath); err != nil { - log.Printf("Error removing socket: %v", err) - } else { - log.Printf("Socket removed successfully") - } os.Exit(0) }() panic(http.ListenAndServe(":2030", mux)) @@ -2,4 +2,7 @@ module github.com/ketsuna-org/bot-creator-api go 1.23.3 -require github.com/arangodb/go-driver/v2 v2.1.3 // indirect +require ( + github.com/arangodb/go-driver/v2 v2.1.3 // indirect + github.com/pebbe/zmq4 v1.3.1 // indirect +) @@ -1,2 +1,6 @@ github.com/arangodb/go-driver/v2 v2.1.3 h1:PpLSe8E2RalFuqTGi2yfHDe3ltOomfFCIToB66p1lr8= github.com/arangodb/go-driver/v2 v2.1.3/go.mod h1:aoDzrsO7PQEFat3Q9pp4zfv6W+WotA7GcCeJQJfX+tc= +github.com/pebbe/zmq4 v1.3.1 h1:WmGnjErIFjb43M6hq5nB/g9cUx9YQAXurAwfZV2Cgeo= +github.com/pebbe/zmq4 v1.3.1/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= +gopkg.in/zeromq/goczmq.v4 v4.1.0 h1:CE+FE81mGVs2aSlnbfLuS1oAwdcVywyMM2AC1g33imI= +gopkg.in/zeromq/goczmq.v4 v4.1.0/go.mod h1:h4IlfePEYMpFdywGr5gAwKhBBj+hiBl/nF4VoSE4k+0= diff --git a/app/internal/create_bot.go b/app/internal/create_bot.go index 7f9aa15..4bbe5ae 100644 --- a/app/internal/create_bot.go +++ b/app/internal/create_bot.go @@ -2,30 +2,26 @@ package internal import ( "fmt" - "net" + "log" "os" "os/exec" "syscall" - "time" + + zmq "github.com/pebbe/zmq4" ) type Bot struct { - BotID string `json:"bot_id"` BotToken string `json:"bot_token"` Cmd *exec.Cmd // Ajouter une référence à la commande - processID int // Stocker le PGID (Process Group ID) + processID int + dealer *zmq.Socket // Stocker le PGID (Process Group ID) + read bool } -func Start(b *Bot) (net.Conn, error) { - socketPath := fmt.Sprintf("/tmp/%s.sock", b.BotID) - - // Nettoyage préalable du socket - if err := os.RemoveAll(socketPath); err != nil && !os.IsNotExist(err) { - return nil, fmt.Errorf("error cleaning socket: %w", err) - } +func Start(b *Bot, dealer *zmq.Socket) (*Bot, error) { // Configuration du bot - cmd := exec.Command("./bot/build/discord-bot", b.BotToken) + cmd := exec.Command("../bot/build/discord-bot", b.BotToken) cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, // Permet de kill le processus enfant si nécessaire } @@ -39,23 +35,51 @@ func Start(b *Bot) (net.Conn, error) { } b.Cmd = cmd b.processID = cmd.Process.Pid - - // Mécanisme d'attente intelligente pour le socket - var conn net.Conn - maxRetries := 10 - for i := 0; i < maxRetries; i++ { - var err error - conn, err = net.Dial("unix", socketPath) - if err == nil { + b.dealer = dealer + // Here we will receive messages from the bot in a separate goroutine + for { + msg, err := dealer.Recv(0) + if err != nil { + return nil, fmt.Errorf("[SERVER] failed to receive message: %w", err) + } + if msg == "ready" { + log.Printf("[SERVER] Bot is ready") + b.read = true break } - time.Sleep(500 * time.Millisecond) + } + return b, nil +} + +func (b *Bot) Stop() error { + if b.Cmd != nil && b.processID != 0 { + if err := syscall.Kill(-b.processID, syscall.SIGTERM); err != nil { + return fmt.Errorf("[SERVER] failed to stop bot: %w", err) + } + } + return nil +} + +func (b *Bot) SendMessage(message string) error { + if b.dealer == nil { + return fmt.Errorf("[SERVER] sender socket is not initialized") + } + if !b.read { + // Let's read the message before sending + msg, err := b.dealer.Recv(0) + if err != nil { + return fmt.Errorf("[SERVER] failed to receive message: %w", err) + } + log.Printf("[SERVER] received message: %s", msg) + b.read = true // Fix ici ! } - if conn == nil { - return nil, fmt.Errorf("failed to connect to bot socket after %d attempts", maxRetries) + _, err := b.dealer.Send(message, 0) + if err != nil { + return fmt.Errorf("[SERVER] failed to send message: %w", err) } + log.Printf("[SERVER] sent message: %s", message) - fmt.Printf("Bot %s started successfully\n", b.BotID) - return conn, nil + b.read = false + return nil } diff --git a/bot/CMakeLists.txt b/bot/CMakeLists.txt index 2ac0346..18307e5 100644 --- a/bot/CMakeLists.txt +++ b/bot/CMakeLists.txt @@ -29,6 +29,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::Crypto z opus + zmq ) # Include directories diff --git a/bot/include/server.cpp b/bot/include/server.cpp deleted file mode 100644 index 9425e85..0000000 --- a/bot/include/server.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include <iostream> -#include <thread> -#include <mutex> -#include <vector> -#include <queue> -#include <algorithm> // Nécessaire pour std::remove_if -#include <functional> -#include <sys/socket.h> -#include <sys/un.h> -#include <poll.h> -#include <fcntl.h> -#include <unistd.h> -#include <cstring> - -#ifdef __APPLE__ -#define SOCKET_PATH_MAX 104 -#else -#define SOCKET_PATH_MAX 108 -#endif - -class UnixSocketServer { -public: - using MessageHandler = std::function<void(const std::string&)>; - - UnixSocketServer(const std::string& path) : - socket_path_(path.substr(0, SOCKET_PATH_MAX - 1)), - running_(false) {} - - ~UnixSocketServer() { stop(); } - - void start(MessageHandler handler) { - if (running_) return; - - server_fd_ = create_socket(); - setup_socket(); - - running_ = true; - server_thread_ = std::thread(&UnixSocketServer::event_loop, this, handler); - } - - void stop() { - running_ = false; - if (server_thread_.joinable()) { - server_thread_.join(); - } - cleanup(); - } - - void send(const std::string& message) { - std::lock_guard<std::mutex> lock(clients_mutex_); - for (auto& client : clients_) { - queue_message(client.fd, message); - } - } - -private: - struct Client { - int fd; - std::queue<std::string> write_queue; - }; - - int create_socket() { - int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); - if (fd == -1) throw std::runtime_error("::socket() failed"); - return fd; - } - - void setup_socket() { - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, socket_path_.c_str(), sizeof(addr.sun_path) - 1); - - unlink(socket_path_.c_str()); - - if (bind(server_fd_, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - close(server_fd_); - throw std::runtime_error("bind() failed"); - } - - if (listen(server_fd_, 5) == -1) { - close(server_fd_); - throw std::runtime_error("listen() failed"); - } - - set_nonblocking(server_fd_); - } - - void set_nonblocking(int fd) { - int flags = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); - } - - void event_loop(MessageHandler handler) { - std::vector<struct pollfd> fds; - fds.push_back({server_fd_, POLLIN, 0}); - - while (running_) { - int ready = ::poll(fds.data(), fds.size(), 250); - if (ready == -1) break; - - for (size_t i = 0; i < fds.size(); ++i) { - if (fds[i].revents & POLLIN) { - if (fds[i].fd == server_fd_) { - accept_new_connection(fds); - } else { - handle_client_input(fds[i].fd, handler); - } - } - - if (fds[i].revents & POLLOUT) { - handle_client_output(fds[i].fd); - } - } - } - } - - void accept_new_connection(std::vector<struct pollfd>& fds) { - int client_fd = accept(server_fd_, nullptr, nullptr); - if (client_fd == -1) return; - - set_nonblocking(client_fd); - fds.push_back({client_fd, POLLIN | POLLOUT, 0}); - - std::lock_guard<std::mutex> lock(clients_mutex_); - clients_.push_back({client_fd, {}}); - } - - void handle_client_input(int fd, MessageHandler handler) { - char buffer[4096]; - ssize_t count = recv(fd, buffer, sizeof(buffer), 0); - - if (count > 0) { - handler(std::string(buffer, count)); - } else { - remove_client(fd); - } - } - - void handle_client_output(int fd) { - std::lock_guard<std::mutex> lock(clients_mutex_); - for (auto& client : clients_) { - if (client.fd == fd && !client.write_queue.empty()) { - const std::string& msg = client.write_queue.front(); - ssize_t sent = ::send(fd, msg.data(), msg.size(), 0); - if (sent > 0) { - client.write_queue.pop(); - } - } - } - } - - void queue_message(int fd, const std::string& message) { - std::lock_guard<std::mutex> lock(clients_mutex_); - for (auto& client : clients_) { - if (client.fd == fd) { - client.write_queue.push(message); - return; - } - } - } - - void remove_client(int fd) { - std::lock_guard<std::mutex> lock(clients_mutex_); - clients_.erase( - std::remove_if(clients_.begin(), clients_.end(), - [fd](const Client& c) { return c.fd == fd; }), - clients_.end() - ); - close(fd); - } - - void cleanup() { - close(server_fd_); - unlink(socket_path_.c_str()); - } - - std::string socket_path_; - int server_fd_ = -1; - std::atomic<bool> running_; - std::thread server_thread_; - std::mutex clients_mutex_; - std::vector<Client> clients_; -}; diff --git a/bot/src/main.cpp b/bot/src/main.cpp index 06a03b0..853e829 100644 --- a/bot/src/main.cpp +++ b/bot/src/main.cpp @@ -1,12 +1,11 @@ #include <dpp/dpp.h> #include <string> +#include <zmq.hpp> #include "../include/utils.hpp" -#include "../include/server.cpp" +#include <thread> -int main(int argc, char *argv[]) -{ - if (argc > 1) - { +int main(int argc, char *argv[]) { + if (argc > 1) { std::string token = argv[1]; setenv("BOT_TOKEN", token.c_str(), 1); } @@ -14,32 +13,21 @@ int main(int argc, char *argv[]) const std::string BOT_TOKEN = getenv("BOT_TOKEN"); dpp::cluster bot(BOT_TOKEN); - std::unique_ptr<UnixSocketServer> server; std::unique_ptr<nlohmann::json> json_data = std::make_unique<nlohmann::json>(); + bot.on_log(dpp::utility::cout_logger()); - bot.on_slashcommand([&json_data](const dpp::slashcommand_t &event) - { - // let's generate the key-value map + bot.on_slashcommand([&json_data](const dpp::slashcommand_t &event) { std::unordered_map<std::string, std::string> key_values = app::generate_key_values(event); - // let's create a string to send std::string response = "Interaction found, but no response found."; - // let's first check if it's a command or not. - if(event.command.get_command_name() != ""){ - // let's check if the command is in the json_data - // display json_data as string in the console - if (json_data->contains(event.command.get_command_name())) - { - // let's check if it does exist + if (event.command.get_command_name() != "") { + if (json_data->contains(event.command.get_command_name())) { std::cout << "Command found: " << event.command.get_command_name() << std::endl; auto command_data = json_data->at(event.command.get_command_name()); - if (command_data.contains("response")) - { + if (command_data.contains("response")) { std::cout << "Response found: " << command_data.at("response") << std::endl; response = command_data.at("response"); - }else - { - // let's display the command data + } else { std::cout << "Command data: " << command_data.dump(4) << std::endl; std::cout << "No response found for command: " << event.command.get_command_name() << std::endl; } @@ -48,36 +36,42 @@ int main(int argc, char *argv[]) event.reply(app::update_string(response, key_values)); }); - bot.on_ready([&bot, &server, &json_data](const dpp::ready_t &event) - { - if (dpp::run_once<struct register_bot_commands>()) - { - // let's start the server - std::string socket_path = "/tmp/" + bot.me.id.str() + ".sock"; - server = std::make_unique<UnixSocketServer>(socket_path); // Création explicite + bot.on_ready([&bot, &json_data](const dpp::ready_t &event) { + if (dpp::run_once<struct register_bot_commands>()) { + // Lancer la boucle ZMQ dans un thread séparé + std::thread zmq_thread([&json_data]() { + zmq::context_t ctx; + zmq::socket_t responder(ctx, zmq::socket_type::req); + responder.connect("tcp://localhost:5555"); + zmq::message_t ready_msg(5); + memcpy(ready_msg.data(), "ready", 5); + responder.send(ready_msg, zmq::send_flags::none); - server->start([&json_data, &server](const std::string& msg) - { - // Traitement du message reçu - nlohmann::json j = app::json_from_string(msg); - if (j.contains("command")) - { - std::string command = j["command"]; - // ... [traitement de la commande] - if (command == "update") - { - // ... [traitement de la commande update] - json_data = std::make_unique<nlohmann::json>(j["data"]); - } - else if (command == "stop") - { - server->stop(); - std::cout << "Server stopped." << std::endl; - exit(0); + while (true) { + zmq::message_t reply; + if (responder.recv(reply, zmq::recv_flags::none)) { + std::string json_str(static_cast<char*>(reply.data()), reply.size()); + try { + nlohmann::json j = app::json_from_string(json_str); + if (j.contains("command")) { + std::string command = j["command"]; + if (command == "update") { + json_data = std::make_unique<nlohmann::json>(j["data"]); + } + } + // Répondre de nouveau si nécessaire + zmq::message_t ping(4); + memcpy(ping.data(), "pong", 4); + responder.send(ping, zmq::send_flags::none); + } catch (const std::exception& e) { + std::cerr << "[BOT] Error parsing JSON: " << e.what() << std::endl; + } } - // ... [gestion des messages entrants] } + }); + + zmq_thread.detach(); // Le thread tourne en fond } }); @@ -14,7 +14,11 @@ openssl.dev # Inclure les headers de développement zlib.dev libopus + libsodium pkg-config + zeromq + cppzmq + ninja (stdenv.mkDerivation { name = "dpp"; version = "latest"; @@ -44,7 +48,8 @@ ''; scripts.start.exec = '' - go run app/cmd/main.go + cd app + go run cmd/main.go ''; # Supprimer la configuration brew inutile dans Nix |
