summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgarder500 <jeremy27.clara22@gmail.com>2025-04-27 16:06:19 +0200
committergarder500 <jeremy27.clara22@gmail.com>2025-04-27 16:06:19 +0200
commit7cbd16f1b3115d3335c61e7c82e349594a726f52 (patch)
tree7a36ded6b73d924b6891ac052140f69bf73c33ac
parentb3791125200154278d31514217c4f417d8961e84 (diff)
Ajout de la logique de connexion et de gestion des signaux pour le bot Discord, ainsi que des améliorations dans la gestion des chaînes JSON et des mises à jour des commandes.
-rw-r--r--app/cmd/main.go57
-rw-r--r--app/internal/create_bot.go61
-rw-r--r--bot/CMakeLists.txt10
-rw-r--r--bot/include/server.cpp182
-rw-r--r--bot/include/utils.cpp38
-rw-r--r--bot/include/utils.hpp22
-rw-r--r--bot/src/main.cpp81
7 files changed, 421 insertions, 30 deletions
diff --git a/app/cmd/main.go b/app/cmd/main.go
index c13de97..604f66f 100644
--- a/app/cmd/main.go
+++ b/app/cmd/main.go
@@ -1,7 +1,14 @@
package main
import (
+ "encoding/json"
+ "fmt"
+ "log"
"net/http"
+ "os"
+ "os/signal"
+
+ "github.com/ketsuna-org/bot-creator-api/internal"
)
func init() {
@@ -15,6 +22,56 @@ func main() {
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
+ botId := "XXXXX"
+ botToken := "XXXXX"
+
+ bot := &internal.Bot{
+ BotID: botId,
+ BotToken: botToken,
+ }
+ conn, err := internal.Start(bot)
+ if err != nil {
+ log.Fatalf("Error starting bot: %v", err)
+ }
+ defer conn.Close()
+ // Handle the bot connection
+ data, err := json.Marshal(map[string]interface{}{
+ "command": "update",
+ "data": map[string]interface{}{
+ "ping": map[string]string{
+ "response": "pong ((userName))",
+ },
+ },
+ })
+ if err != nil {
+ log.Fatalf("Error marshaling JSON: %v", err)
+ }
+ conn.Write(data)
+
+ // Handle if signal is received
+ signals := make(chan os.Signal, 1)
+ signal.Notify(signals, os.Interrupt)
+ signal.Notify(signals, os.Kill)
+ go func() {
+ sig := <-signals
+ log.Printf("Received signal: %s", sig)
+ // 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)
+ } else {
+ log.Printf("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/internal/create_bot.go b/app/internal/create_bot.go
new file mode 100644
index 0000000..c1bea85
--- /dev/null
+++ b/app/internal/create_bot.go
@@ -0,0 +1,61 @@
+package internal
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "os/exec"
+ "syscall"
+ "time"
+)
+
+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)
+}
+
+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)
+ }
+
+ // Configuration du bot
+ cmd := exec.Command("../bot/build/discord-bot", b.BotToken)
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setpgid: true, // Permet de kill le processus enfant si nécessaire
+ }
+
+ // Redirection des sorties pour le débogage
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Start(); err != nil {
+ return nil, fmt.Errorf("failed to start bot: %w", err)
+ }
+ 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 {
+ break
+ }
+ time.Sleep(500 * time.Millisecond)
+ }
+
+ if conn == nil {
+ return nil, fmt.Errorf("failed to connect to bot socket after %d attempts", maxRetries)
+ }
+
+ fmt.Printf("Bot %s started successfully\n", b.BotID)
+ return conn, nil
+}
diff --git a/bot/CMakeLists.txt b/bot/CMakeLists.txt
index 5fef1c3..2dc8fb1 100644
--- a/bot/CMakeLists.txt
+++ b/bot/CMakeLists.txt
@@ -6,10 +6,6 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
-# Find required dependencies
-find_package(OpenSSL REQUIRED)
-find_package(ZLIB REQUIRED)
-
# Configure DPP library (assuming you have it installed via Nix)
find_package(dpp REQUIRED
HINTS ${DPP_INCLUDE_DIR} # Add path if needed: /nix/store/.../include
@@ -29,17 +25,11 @@ add_executable(${PROJECT_NAME}
# Link libraries with proper dependencies
target_link_libraries(${PROJECT_NAME} PRIVATE
dpp
- OpenSSL::SSL
- OpenSSL::Crypto
- ZLIB::ZLIB
- pthread # Required for DPP's threading
)
# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE
${DPP_INCLUDE_DIRS}
- ${OPENSSL_INCLUDE_DIR}
- ${Sodium_INCLUDE_DIRS}
)
# macOS ARM64 specific fixes
diff --git a/bot/include/server.cpp b/bot/include/server.cpp
new file mode 100644
index 0000000..55e5df9
--- /dev/null
+++ b/bot/include/server.cpp
@@ -0,0 +1,182 @@
+#include <iostream>
+#include <thread>
+#include <mutex>
+#include <vector>
+#include <queue>
+#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/include/utils.cpp b/bot/include/utils.cpp
index 6a47d42..081fd4e 100644
--- a/bot/include/utils.cpp
+++ b/bot/include/utils.cpp
@@ -20,7 +20,7 @@ namespace app
return g.get_icon_url(1024, i_webp);
}
- std::string update_string(const std::string &initial, const std::map<std::string, std::string> &updates)
+ std::string update_string(const std::string &initial, const std::unordered_map<std::string, std::string> &updates)
{
static const std::regex placeholderRegex(R"(\(\((.*?)\)\))", std::regex::icase);
@@ -62,12 +62,12 @@ namespace app
return result;
}
// Forward declaration
- void process_interaction_option(const slashcommand_t &event, const command_data_option &option, std::map<std::string, std::string> &kv);
+ void process_interaction_option(const slashcommand_t &event, const command_data_option &option, std::unordered_map<std::string, std::string> &kv);
// Génère la map clé/valeur
- std::map<std::string, std::string> generate_key_values(const slashcommand_t &event)
+ std::unordered_map<std::string, std::string> generate_key_values(const slashcommand_t &event)
{
- std::map<std::string, std::string> key_values;
+ std::unordered_map<std::string, std::string> key_values;
const guild *g = event.command.is_guild_interaction() ? &event.command.get_guild() : nullptr;
const channel &c = event.command.get_channel();
const user &u = event.command.get_issuing_user();
@@ -98,7 +98,7 @@ namespace app
}
// Traite une option d'interaction récursivement
- void process_interaction_option(const slashcommand_t &event, const command_data_option &option, std::map<std::string, std::string> &kv)
+ void process_interaction_option(const slashcommand_t &event, const command_data_option &option, std::unordered_map<std::string, std::string> &kv)
{
switch (option.type)
{
@@ -184,4 +184,32 @@ namespace app
break;
}
}
+
+ nlohmann::json json_from_string(const std::string &str)
+ {
+ nlohmann::json j;
+ try
+ {
+ j = nlohmann::json::parse(str);
+ }
+ catch (const nlohmann::json::parse_error &e)
+ {
+ std::cerr << "JSON parse error: " << e.what() << std::endl;
+ }
+ return j;
+ }
+
+ std::string string_from_json(const nlohmann::json &j)
+ {
+ std::string str;
+ try
+ {
+ str = j.dump();
+ }
+ catch (const nlohmann::json::exception &e)
+ {
+ std::cerr << "JSON exception: " << e.what() << std::endl;
+ }
+ return str;
+ }
}
diff --git a/bot/include/utils.hpp b/bot/include/utils.hpp
index 19c834c..57f4005 100644
--- a/bot/include/utils.hpp
+++ b/bot/include/utils.hpp
@@ -37,7 +37,7 @@ namespace app
* @param updates A map of key-value pairs to replace placeholders
* @return std::string The updated string with placeholders replaced
*/
- std::string update_string(const std::string &initial, const std::map<std::string, std::string> &updates);
+ std::string update_string(const std::string &initial, const std::unordered_map<std::string, std::string> &updates);
/**
* @brief Processes a command option recursively and adds values to the key-value map
@@ -46,7 +46,7 @@ namespace app
* @param option The command option to process
* @param kv The key-value map to update
*/
- void process_interaction_option(const slashcommand_t &event, const command_data_option &option, std::map<std::string, std::string> &kv);
+ void process_interaction_option(const slashcommand_t &event, const command_data_option &option, std::unordered_map<std::string, std::string> &kv);
/**
* @brief Generates a map of key-value pairs from a slash command event
@@ -54,7 +54,23 @@ namespace app
* @param event The slash command event
* @return std::map<std::string, std::string> A map containing information about the command, user, guild, and options
*/
- std::map<std::string, std::string> generate_key_values(const slashcommand_t &event);
+ std::unordered_map<std::string, std::string> generate_key_values(const slashcommand_t &event);
+
+ /**
+ * @brief Parses a JSON string into a JSON object
+ *
+ * @param str The JSON string to parse
+ * @return nlohmann::json The parsed JSON object
+ */
+ nlohmann::json json_from_string(const std::string &str);
+
+ /**
+ * @brief Converts a JSON object into a string
+ *
+ * @param j The JSON object to convert
+ * @return std::string The string representation of the JSON object
+ */
+ std::string string_from_json(const nlohmann::json &j);
} // namespace dpp
diff --git a/bot/src/main.cpp b/bot/src/main.cpp
index 500170d..06a03b0 100644
--- a/bot/src/main.cpp
+++ b/bot/src/main.cpp
@@ -1,26 +1,83 @@
#include <dpp/dpp.h>
+#include <string>
#include "../include/utils.hpp"
-const std::string BOT_TOKEN = getenv("BOT_TOKEN");
+#include "../include/server.cpp"
-int main() {
- dpp::cluster bot(BOT_TOKEN);
+int main(int argc, char *argv[])
+{
+ if (argc > 1)
+ {
+ std::string token = argv[1];
+ setenv("BOT_TOKEN", token.c_str(), 1);
+ }
+
+ 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([](const dpp::slashcommand_t& event) {
+ bot.on_slashcommand([&json_data](const dpp::slashcommand_t &event)
+ {
// let's generate the key-value map
- std::map<std::string, std::string> key_values = app::generate_key_values(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 = "Pong! ((userName))";
-
- if (event.command.get_command_name() == "ping") {
- event.reply(app::update_string(response, key_values));
+ 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
+ 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"))
+ {
+ std::cout << "Response found: " << command_data.at("response") << std::endl;
+ response = command_data.at("response");
+ }else
+ {
+ // let's display the command data
+ std::cout << "Command data: " << command_data.dump(4) << std::endl;
+ std::cout << "No response found for command: " << event.command.get_command_name() << std::endl;
+ }
+ }
}
+ event.reply(app::update_string(response, key_values));
});
- bot.on_ready([&bot](const dpp::ready_t& event) {
- if (dpp::run_once<struct register_bot_commands>()) {
- bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id));
+ 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
+
+ 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);
+ }
+ // ... [gestion des messages entrants]
+ }
+ });
}
});