summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoler_j <soler_j@etna-alternance.net>2025-04-29 03:07:48 +0200
committersoler_j <soler_j@etna-alternance.net>2025-04-29 03:07:48 +0200
commit2a58d43c3b820eb8ffec01bb51905146a7278533 (patch)
treed05b0b815aee8520ae1375b69a503511f7b260fa
parent0c53f3e452830b300c598c9370946ae54f443a78 (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.json10
-rw-r--r--Dockerfile37
-rw-r--r--app/cmd/main.go55
-rw-r--r--app/go.mod5
-rw-r--r--app/go.sum4
-rw-r--r--app/internal/create_bot.go74
-rw-r--r--bot/CMakeLists.txt1
-rw-r--r--bot/include/server.cpp183
-rw-r--r--bot/src/main.cpp90
-rw-r--r--devenv.nix7
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"
}
}
diff --git a/Dockerfile b/Dockerfile
index 9df7ff0..b65c89e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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))
diff --git a/app/go.mod b/app/go.mod
index 0fdbadd..86e0320 100644
--- a/app/go.mod
+++ b/app/go.mod
@@ -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
+)
diff --git a/app/go.sum b/app/go.sum
index e3078d7..b8dd409 100644
--- a/app/go.sum
+++ b/app/go.sum
@@ -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
}
});
diff --git a/devenv.nix b/devenv.nix
index dfa89c7..357702d 100644
--- a/devenv.nix
+++ b/devenv.nix
@@ -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