diff options
| -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  | 
