diff options
| -rw-r--r-- | .envrc | 5 | ||||
| -rw-r--r-- | .gitignore | 11 | ||||
| -rw-r--r-- | .vscode/settings.json | 91 | ||||
| -rw-r--r-- | bot/CMakeLists.txt | 78 | ||||
| -rw-r--r-- | bot/include/utils.cpp | 187 | ||||
| -rw-r--r-- | bot/include/utils.hpp | 62 | ||||
| -rw-r--r-- | bot/src/main.cpp | 28 | ||||
| -rw-r--r-- | devenv.lock | 103 | ||||
| -rw-r--r-- | devenv.nix | 49 | ||||
| -rw-r--r-- | devenv.yaml | 15 | 
10 files changed, 629 insertions, 0 deletions
@@ -0,0 +1,5 @@ +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" + +use devenv @@ -30,3 +30,14 @@  *.exe  *.out  *.app + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml +build diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0954d11 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,91 @@ +{ +    "cmake.sourceDirectory": "/Users/jeremy/Documents/bot-creator-api/bot", +    "files.associations": { +        "map": "cpp", +        "__bit_reference": "cpp", +        "__config": "cpp", +        "__debug": "cpp", +        "__errc": "cpp", +        "__hash_table": "cpp", +        "__locale": "cpp", +        "__mutex_base": "cpp", +        "__node_handle": "cpp", +        "__split_buffer": "cpp", +        "__threading_support": "cpp", +        "__tree": "cpp", +        "__verbose_abort": "cpp", +        "any": "cpp", +        "array": "cpp", +        "atomic": "cpp", +        "bitset": "cpp", +        "cctype": "cpp", +        "charconv": "cpp", +        "clocale": "cpp", +        "cmath": "cpp", +        "complex": "cpp", +        "condition_variable": "cpp", +        "csignal": "cpp", +        "cstdarg": "cpp", +        "cstddef": "cpp", +        "cstdint": "cpp", +        "cstdio": "cpp", +        "cstdlib": "cpp", +        "cstring": "cpp", +        "ctime": "cpp", +        "cwchar": "cpp", +        "cwctype": "cpp", +        "deque": "cpp", +        "exception": "cpp", +        "coroutine": "cpp", +        "forward_list": "cpp", +        "fstream": "cpp", +        "future": "cpp", +        "initializer_list": "cpp", +        "iomanip": "cpp", +        "ios": "cpp", +        "iosfwd": "cpp", +        "iostream": "cpp", +        "istream": "cpp", +        "limits": "cpp", +        "list": "cpp", +        "locale": "cpp", +        "mutex": "cpp", +        "new": "cpp", +        "optional": "cpp", +        "ostream": "cpp", +        "queue": "cpp", +        "ratio": "cpp", +        "set": "cpp", +        "shared_mutex": "cpp", +        "span": "cpp", +        "sstream": "cpp", +        "stack": "cpp", +        "stdexcept": "cpp", +        "streambuf": "cpp", +        "string": "cpp", +        "string_view": "cpp", +        "system_error": "cpp", +        "thread": "cpp", +        "tuple": "cpp", +        "typeinfo": "cpp", +        "unordered_map": "cpp", +        "valarray": "cpp", +        "variant": "cpp", +        "vector": "cpp", +        "bit": "cpp", +        "*.tcc": "cpp", +        "chrono": "cpp", +        "compare": "cpp", +        "concepts": "cpp", +        "algorithm": "cpp", +        "functional": "cpp", +        "iterator": "cpp", +        "memory": "cpp", +        "memory_resource": "cpp", +        "random": "cpp", +        "type_traits": "cpp", +        "utility": "cpp", +        "format": "cpp", +        "stop_token": "cpp" +    } +} diff --git a/bot/CMakeLists.txt b/bot/CMakeLists.txt new file mode 100644 index 0000000..5fef1c3 --- /dev/null +++ b/bot/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 3.15) +project(discord-bot VERSION 1.0 DESCRIPTION "A Discord bot using DPP library") + +# Required for DPP and modern C++ features +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 +) +file(GLOB_RECURSE SRC_FILES +    include/*.cpp +    include/*.hpp +) +# Add executable +add_executable(${PROJECT_NAME} +    src/main.cpp +    ${SRC_FILES} +    # Add other source files here +) + + +# 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 +if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") +    target_compile_options(${PROJECT_NAME} PRIVATE +        -arch arm64 +        -I/opt/homebrew/opt/openssl/include  # If using Homebrew +    ) +    target_link_options(${PROJECT_NAME} PRIVATE +        -arch arm64 +        -L/opt/homebrew/opt/openssl/lib      # If using Homebrew +    ) +elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") +    target_compile_options(${PROJECT_NAME} PRIVATE +        -arch x86_64 +        -I/opt/homebrew/opt/openssl/include  # If using Homebrew +    ) +    target_link_options(${PROJECT_NAME} PRIVATE +        -arch x86_64 +        -L/opt/homebrew/opt/openssl/lib      # If using Homebrew +    ) +elseif(UNIX AND NOT APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") +    target_compile_options(${PROJECT_NAME} PRIVATE +        -m64 +    ) +    target_link_options(${PROJECT_NAME} PRIVATE +        -m64 +    ) +elseif(UNIX AND NOT APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") +    target_compile_options(${PROJECT_NAME} PRIVATE +        -marm +    ) +    target_link_options(${PROJECT_NAME} PRIVATE +        -marm +    ) +endif() diff --git a/bot/include/utils.cpp b/bot/include/utils.cpp new file mode 100644 index 0000000..6a47d42 --- /dev/null +++ b/bot/include/utils.cpp @@ -0,0 +1,187 @@ +#include <dpp/dpp.h> +#include <dpp/nlohmann/json.hpp> +#include <map> +#include <string> +#include <regex> +#include <sstream> +#include <algorithm> + +using namespace dpp; +namespace app +{ +    // Helpers +    std::string make_avatar_url(const user &u) +    { +        return u.avatar.to_string().empty() ? u.get_default_avatar_url() : u.get_avatar_url(1024, i_webp, true); +    } + +    std::string make_guild_icon(const guild &g) +    { +        return g.get_icon_url(1024, i_webp); +    } + +    std::string update_string(const std::string &initial, const std::map<std::string, std::string> &updates) +    { +        static const std::regex placeholderRegex(R"(\(\((.*?)\)\))", std::regex::icase); + +        std::string result; +        std::sregex_iterator it(initial.begin(), initial.end(), placeholderRegex); +        std::sregex_iterator end; + +        size_t last_pos = 0; +        for (; it != end; ++it) +        { +            const auto &match = *it; +            result.append(initial, last_pos, match.position() - last_pos); + +            std::string content = match[1].str(); +            std::vector<std::string> keys; +            std::stringstream ss(content); +            std::string key; +            bool replaced = false; + +            while (std::getline(ss, key, '|')) +            { +                key = trim(key); +                auto found = updates.find(key); +                if (found != updates.end()) +                { +                    result.append(found->second); +                    replaced = true; +                    break; +                } +            } +            if (!replaced) +            { +                // Aucune clé trouvée : chaîne vide +            } + +            last_pos = match.position() + match.length(); +        } +        result.append(initial, last_pos, std::string::npos); +        return result; +    } +    // Forward declaration +    void process_interaction_option(const slashcommand_t &event, const command_data_option &option, std::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::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(); +        key_values["commandName"] = event.command.get_command_name(); +        key_values["commandId"] = event.command.id.str(); +        key_values["commandType"] = std::to_string(event.command.type); +        key_values["userName"] = u.username; +        key_values["userId"] = u.id.str(); +        key_values["userAvatar"] = make_avatar_url(u); +        key_values["guildName"] = g ? g->name : "DM"; +        key_values["channelName"] = c.name; +        key_values["channelId"] = c.id.str(); +        key_values["channelType"] = std::to_string(c.get_type()); +        key_values["guildId"] = g ? g->id.str() : "0"; +        key_values["guildIcon"] = g ? make_guild_icon(*g) : ""; +        key_values["guildCount"] = g ? std::to_string(g->member_count) : "0"; +        key_values["guildOwner"] = g ? g->owner_id.str() : "0"; +        key_values["guildCreatedAt"] = g ? std::to_string(g->get_creation_time()) : "0"; +        key_values["guildBoostTier"] = g ? std::to_string(g->premium_tier) : "0"; +        key_values["guildBoostCount"] = g ? std::to_string(g->premium_subscription_count) : "0"; + +        // Options de commande +        for (const auto &option : event.command.get_command_interaction().options) +        { +            process_interaction_option(event, option, key_values); +        } +        return key_values; +    } + +    // 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) +    { +        switch (option.type) +        { +        case co_sub_command: +        case co_sub_command_group: +            for (const auto &subopt : option.options) +            { +                process_interaction_option(event, subopt, kv); +            } +            break; +        case co_user: +        { +            snowflake user_id = std::get<snowflake>(option.value); +            auto user_ptr = event.command.get_resolved_user(user_id); +            const user &u = user_ptr; +            kv["opts." + option.name] = u.username; +            kv["opts." + option.name + ".id"] = u.id.str(); +            kv["opts." + option.name + ".avatar"] = make_avatar_url(u); +            kv["opts." + option.name + ".discriminator"] = std::to_string(u.discriminator); +            kv["opts." + option.name + ".bot"] = u.is_bot() ? "true" : "false"; +            kv["opts." + option.name + ".created_at"] = std::to_string(u.get_creation_time()); +        } +        break; +        case co_channel: +        { +            snowflake chan_id = std::get<snowflake>(option.value); +            auto chan_ptr = event.command.get_resolved_channel(chan_id); +            const channel &c = chan_ptr; +            kv["opts." + option.name] = c.name; +            kv["opts." + option.name + ".id"] = c.id.str(); +            kv["opts." + option.name + ".type"] = std::to_string(c.get_type()); +            kv["opts." + option.name + ".created_at"] = std::to_string(c.get_creation_time()); +        } +        break; +        case co_role: +        { +            snowflake role_id = std::get<snowflake>(option.value); +            auto role_ptr = event.command.get_resolved_role(role_id); +            const role &r = role_ptr; +            kv["opts." + option.name] = r.name; +            kv["opts." + option.name + ".id"] = r.id.str(); +            kv["opts." + option.name + ".color"] = std::to_string(r.colour); +            kv["opts." + option.name + ".hoist"] = r.is_hoisted() ? "true" : "false"; +            kv["opts." + option.name + ".position"] = std::to_string(r.position); +        } +        break; +        case co_mentionable: +        { +            snowflake mentionable_id = std::get<snowflake>(option.value); +            auto member_ptr = event.command.get_resolved_member(mentionable_id); +            const user &u = *member_ptr.get_user(); +            kv["opts." +option.name] = u.username; +            kv["opts." + option.name + ".id"] = u.id.str(); +            kv["opts." + option.name + ".avatar"] = make_avatar_url(u); +            kv["opts." + option.name + ".discriminator"] = std::to_string(u.discriminator); +            kv["opts." + option.name + ".bot"] = u.is_bot() ? "true" : "false"; +            kv["opts." + option.name + ".created_at"] = std::to_string(u.get_creation_time()); +            kv["opts." + option.name + ".nick"] = member_ptr.get_nickname(); +            kv["opts." + option.name + ".joined_at"] = std::to_string(member_ptr.joined_at); +        } +        break; +        case co_string: +            kv["opts." + option.name] = std::get<std::string>(option.value); +            break; +        case co_integer: +            kv["opts." + option.name] = std::to_string(std::get<int64_t>(option.value)); +            break; +        case co_boolean: +            kv["opts." + option.name] = std::get<bool>(option.value) ? "true" : "false"; +            break; +        case co_number: +            kv["opts." + option.name] = std::to_string(std::get<double>(option.value)); +            break; +        case co_attachment: +        { +            snowflake attachment_id = std::get<snowflake>(option.value); +            auto att_ptr = event.command.get_resolved_attachment(attachment_id); +            kv["opts." + option.name] = att_ptr.url; +            kv["opts." + option.name + ".id"] = att_ptr.id.str(); +            kv["opts." + option.name + ".filename"] = att_ptr.filename; +            kv["opts." + option.name + ".size"] = std::to_string(att_ptr.size); +        } +        break; +        } +    } +} diff --git a/bot/include/utils.hpp b/bot/include/utils.hpp new file mode 100644 index 0000000..19c834c --- /dev/null +++ b/bot/include/utils.hpp @@ -0,0 +1,62 @@ +// utils.hpp + +#ifndef UTILS_HPP +#define UTILS_HPP + +#include <dpp/dpp.h> +#include <dpp/nlohmann/json.hpp> +#include <map> +#include <string> +#include <regex> +#include <sstream> +#include <algorithm> +using namespace dpp; +namespace app +{ + +    /** +     * @brief Creates a URL for a user's avatar +     * +     * @param u The user object +     * @return std::string The URL to the user's avatar +     */ +    std::string make_avatar_url(const user &u); + +    /** +     * @brief Creates a URL for a guild's icon +     * +     * @param g The guild object +     * @return std::string The URL to the guild's icon +     */ +    std::string make_guild_icon(const guild &g); + +    /** +     * @brief Updates a string by replacing placeholders with values from a map +     * +     * @param initial The initial string with placeholders in the format ((key)) +     * @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); + +    /** +     * @brief Processes a command option recursively and adds values to the key-value map +     * +     * @param event The slash command event +     * @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); + +    /** +     * @brief Generates a map of key-value pairs from a slash command event +     * +     * @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); + +} // namespace dpp + +#endif // UTILS_HPP +// utils.hpp diff --git a/bot/src/main.cpp b/bot/src/main.cpp new file mode 100644 index 0000000..500170d --- /dev/null +++ b/bot/src/main.cpp @@ -0,0 +1,28 @@ +#include <dpp/dpp.h> +#include "../include/utils.hpp" +const std::string BOT_TOKEN = getenv("BOT_TOKEN"); + +int main() { +    dpp::cluster bot(BOT_TOKEN); + +    bot.on_log(dpp::utility::cout_logger()); + +    bot.on_slashcommand([](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); +        // 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)); +        } +    }); + +    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.start(dpp::st_wait); +} diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..2388897 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,103 @@ +{ +  "nodes": { +    "devenv": { +      "locked": { +        "dir": "src/modules", +        "lastModified": 1745604263, +        "owner": "cachix", +        "repo": "devenv", +        "rev": "36807c727e743e7a00999922e7f737a0cc4e05ac", +        "type": "github" +      }, +      "original": { +        "dir": "src/modules", +        "owner": "cachix", +        "repo": "devenv", +        "type": "github" +      } +    }, +    "flake-compat": { +      "flake": false, +      "locked": { +        "lastModified": 1733328505, +        "owner": "edolstra", +        "repo": "flake-compat", +        "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", +        "type": "github" +      }, +      "original": { +        "owner": "edolstra", +        "repo": "flake-compat", +        "type": "github" +      } +    }, +    "git-hooks": { +      "inputs": { +        "flake-compat": "flake-compat", +        "gitignore": "gitignore", +        "nixpkgs": [ +          "nixpkgs" +        ] +      }, +      "locked": { +        "lastModified": 1742649964, +        "owner": "cachix", +        "repo": "git-hooks.nix", +        "rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82", +        "type": "github" +      }, +      "original": { +        "owner": "cachix", +        "repo": "git-hooks.nix", +        "type": "github" +      } +    }, +    "gitignore": { +      "inputs": { +        "nixpkgs": [ +          "git-hooks", +          "nixpkgs" +        ] +      }, +      "locked": { +        "lastModified": 1709087332, +        "owner": "hercules-ci", +        "repo": "gitignore.nix", +        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", +        "type": "github" +      }, +      "original": { +        "owner": "hercules-ci", +        "repo": "gitignore.nix", +        "type": "github" +      } +    }, +    "nixpkgs": { +      "locked": { +        "lastModified": 1733477122, +        "owner": "cachix", +        "repo": "devenv-nixpkgs", +        "rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857", +        "type": "github" +      }, +      "original": { +        "owner": "cachix", +        "ref": "rolling", +        "repo": "devenv-nixpkgs", +        "type": "github" +      } +    }, +    "root": { +      "inputs": { +        "devenv": "devenv", +        "git-hooks": "git-hooks", +        "nixpkgs": "nixpkgs", +        "pre-commit-hooks": [ +          "git-hooks" +        ] +      } +    } +  }, +  "root": "root", +  "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..4820f3a --- /dev/null +++ b/devenv.nix @@ -0,0 +1,49 @@ +{ pkgs, lib, config, inputs, ... }: + +{ +  env = { +    CXXFLAGS = "-std=c++20"; +    PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; # Critical pour trouver OpenSSL +    NIX_CFLAGS_COMPILE = "-arch arm64"; +  }; + +  packages = with pkgs; [ +    git +    gcc +    cmake +    clang-tools +    openssl.dev  # Inclure les headers de développement +    zlib.dev +    libopus +    pkg-config +    (stdenv.mkDerivation { +        name = "dpp"; +        version = "latest"; +        src = fetchFromGitHub { +          owner = "brainboxdotcc"; +          repo = "DPP"; +          rev = "c6bcab5b4632fe35e32e63e3bc813e9e2cd2893e"; +          sha256 = "sha256-IMESnvvjATgsKCDfrRY8bPxUYpiULIPFf6SToO7GbVM="; +        }; +        nativeBuildInputs = [ cmake pkg-config ]; +        buildInputs = [ openssl zlib libopus ]; +        cmakeFlags = [ +          "-DCMAKE_CXX_STANDARD=20" +          "-DCMAKE_BUILD_TYPE=Release" +          "-DBUILD_TEST=OFF" +          "-DBUILD_EXAMPLES=OFF" +        ]; +      })  # Nécessaire pour détecter OpenSSL +  ]; + +  languages.go.enable = true; + +  scripts.build.exec = '' +    mkdir -p bot/build +    cd bot/build +    cmake .. +    make -j$NIX_BUILD_CORES +  ''; + +  # Supprimer la configuration brew inutile dans Nix +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..116a2ad --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json +inputs: +  nixpkgs: +    url: github:cachix/devenv-nixpkgs/rolling + +# If you're using non-OSS software, you can set allowUnfree to true. +# allowUnfree: true + +# If you're willing to use a package that's vulnerable +# permittedInsecurePackages: +#  - "openssl-1.1.1w" + +# If you have more than one devenv you can merge them +#imports: +# - ./backend  | 
