summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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]
+ }
+ });
}
});