From 7d2a9b9b7ecf77b61a397644c423d1319c0184dc Mon Sep 17 00:00:00 2001 From: vexryn11 Date: Fri, 28 Aug 2026 02:18:22 -0700 Subject: [PATCH 1/7] Add edit module --- src/edit.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/edit.cpp diff --git a/src/edit.cpp b/src/edit.cpp new file mode 100644 index 0000000..a0385a7 --- /dev/null +++ b/src/edit.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +class Bot { +private: + std::string name; + std::unordered_map> commands; + +public: + explicit Bot(std::string botName) + : name(std::move(botName)) {} + + void registerCommand(const std::string& command, + std::function callback) { + commands[command] = std::move(callback); + } + + void handleCommand(const std::string& input) const { + auto it = commands.find(input); + + if (it != commands.end()) { + it->second(); + } else { + std::cout << "Unknown command: " << input << '\n'; + } + } + + void run() const { + std::cout << name << " is running.\n"; + std::cout << "Type a command:\n"; + + std::string input; + + while (std::cout << "> " && std::getline(std::cin, input)) { + if (input == "exit") + break; + + handleCommand(input); + } + } +}; + +int main() { + Bot bot("Discord Bot"); + + bot.registerCommand("ping", [] { + std::cout << "Pong!\n"; + }); + + bot.registerCommand("hello", [] { + std::cout << "Hello!\n"; + }); + + bot.registerCommand("status", [] { + std::cout << "Bot is online.\n"; + }); + + bot.run(); + + return 0; +} \ No newline at end of file From 21c0b6496c0fbd47103a8cb512e6f97bfcfbced7 Mon Sep 17 00:00:00 2001 From: vexryn11 Date: Thu, 27 Aug 2026 23:31:13 -0700 Subject: [PATCH 2/7] Update main.cpp --- src/main.cpp | 369 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 315 insertions(+), 54 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c1bacad..6c64f62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,10 @@ #include -#include #include +#include +#include + #include +#include #include "commands/commands.h" #include "globals/globals.h" @@ -11,92 +14,350 @@ using json = nlohmann::json; std::vector cmdList = { - { "topic", "Get a topic question", cmd::topicCommand }, - { "beginner", "Get a beginner's guide to C++", cmd::beginnerCommand }, - { "coding", "Get a coding question", cmd::codingCommand }, - { "close", "Close a ticket or forum post", cmd::closeCommand }, - { "ticket", "Open a ticket", cmd::ticketCommand, { dpp::command_option(dpp::command_option_type::co_user, "participant", "Add participant", false) }}, - { "code", "Formatting code on Discord", cmd::codeCommand }, - { "project", "Get a project idea", cmd::projectCommand }, - { "rule", "Get the server rules", cmd::ruleCommand, { dpp::command_option(dpp::command_option_type::co_integer, "number", "Rule to mention", false) }} + { + "topic", + "Get a topic question", + cmd::topicCommand + }, + + { + "beginner", + "Get a beginner's guide to C++", + cmd::beginnerCommand + }, + + { + "coding", + "Get a coding question", + cmd::codingCommand + }, + + { + "close", + "Close a ticket or forum post", + cmd::closeCommand + }, + + { + "ticket", + "Open a ticket", + cmd::ticketCommand, + { + dpp::command_option( + dpp::command_option_type::co_user, + "participant", + "Add participant", + false + ) + } + }, + + { + "code", + "Formatting code on Discord", + cmd::codeCommand + }, + + { + "project", + "Get a project idea", + cmd::projectCommand + }, + + { + "rule", + "Get the server rules", + cmd::ruleCommand, + { + dpp::command_option( + dpp::command_option_type::co_integer, + "number", + "Rule to mention", + false + ) + } + } }; + + +std::vector createSlashCommands( + const dpp::snowflake& applicationId +) +{ + std::vector commands; + + for (const auto& item : cmdList) + { + dpp::slashcommand command; + + command + .set_name(item.name) + .set_description(item.desc) + .set_application_id(applicationId); + + for (const auto& option : item.args) + { + command.add_option(option); + } + + if (item.permissions) + { + command.set_default_permissions( + dpp::permission(item.permissions) + ); + } + + commands.push_back(command); + } + + return commands; +} + + +bool executeCommand( + dpp::cluster& bot, + const dpp::slashcommand_t& event +) +{ + const std::string commandName = + event.command.get_command_name(); + + for (const auto& item : cmdList) + { + if (item.name == commandName) + { + item.function(bot, event); + return true; + } + } + + return false; +} + + int main() { + std::cout << "[*] Starting bot..." << std::endl; + + + std::ifstream configFile("config.json"); - json config = json::parse(configFile); + + if (!configFile.is_open()) + { + std::cerr + << "[!] Failed to open config.json" + << std::endl; + + return 1; + } + + json config; + + try + { + configFile >> config; + } + catch (const json::parse_error& error) + { + std::cerr + << "[!] Invalid JSON in config.json: " + << error.what() + << std::endl; + + return 1; + } + std::string globalsConfigError; - if (!globals::loadFromConfig(config, globalsConfigError)) + + if (!globals::loadFromConfig( + config, + globalsConfigError)) + { + std::cerr + << "[!] Invalid configuration: " + << globalsConfigError + << std::endl; + + return 1; + } + + + if (!config.contains("token") || + !config["token"].is_string() || + config["token"].get().empty()) { - std::cerr << "[!] Invalid configuration: " << globalsConfigError << std::endl; + std::cerr + << "[!] Discord token is missing from config.json" + << std::endl; + return 1; } - dpp::cluster bot(config["token"], dpp::i_default_intents | dpp::i_message_content); + const std::string token = + config["token"].get(); + + + + dpp::cluster bot( + token, + dpp::i_default_intents | + dpp::i_message_content + ); + ModerationService moderationService(bot); - bot.on_ready([&bot](const dpp::ready_t& event) { - std::cout << "[!] Bot ready" << std::endl; - bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_watching, "cppdiscord.com")); + bot.on_ready([&bot](const dpp::ready_t& event) + { + std::cout + << "[+] Bot ready as " + << bot.me.username + << std::endl; + + bot.set_presence( + dpp::presence( + dpp::presence_status::ps_online, + dpp::activity_type::at_watching, + "cppdiscord.com" + ) + ); + // Register slash commands once. if (dpp::run_once()) { - std::vector slashcommands; - for (const auto& item : cmdList) - { - dpp::slashcommand slashCommand; - slashCommand.set_name(item.name); - slashCommand.set_description(item.desc); - slashCommand.set_application_id(bot.me.id); + std::cout + << "[*] Registering slash commands..." + << std::endl; - for (const dpp::command_option& arg : item.args) - slashCommand.add_option(arg); + auto slashCommands = + createSlashCommands(bot.me.id); - if (item.permissions) - slashCommand.set_default_permissions(dpp::permission(item.permissions)); + bot.global_bulk_command_create( + slashCommands, + [](const dpp::confirmation_callback_t& callback) + { + if (callback.is_error()) + { + std::cerr + << "[!] Failed to register commands: " + << callback.get_error().message + << std::endl; - slashcommands.push_back(slashCommand); - } - bot.global_bulk_command_create(slashcommands); + return; + } + + std::cout + << "[+] Slash commands registered." + << std::endl; + } + ); } }); - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - for (const auto& item : cmdList) + + bot.on_slashcommand( + [&bot](const dpp::slashcommand_t& event) + { + const std::string commandName = + event.command.get_command_name(); + + std::cout + << "[COMMAND] /" + << commandName + << " used by " + << event.command.get_issuing_user().username + << std::endl; + + if (!executeCommand(bot, event)) + { + std::cerr + << "[!] Unknown command: /" + << commandName + << std::endl; + } + } + ); + + + + bot.on_message_create( + [&bot, &moderationService](const dpp::message_create_t& event) { - if (item.name == event.command.get_command_name()) + // Moderation gets first priority. + if (moderationService.handleMessage(event)) { - item.function(bot, event); return; } + + const dpp::channel* channel = + dpp::find_channel(event.msg.channel_id); + + if (!channel) + { + return; + } + + if (channel->name == "suggestions") + { + utils::suggestion::createSuggestion( + bot, + event + ); + } } - }); + ); - bot.on_message_create([&bot, &moderationService](const dpp::message_create_t& event) { - if (moderationService.handleMessage(event)) - return; - const dpp::channel* channel = dpp::find_channel(event.msg.channel_id); + bot.on_button_click( + [&bot](const dpp::button_click_t& event) + { + const std::string& customId = + event.custom_id; - if (channel && channel->name == "suggestions") - utils::suggestion::createSuggestion(bot, event); - }); + if (customId == "delSuggestion") + { + utils::suggestion::deleteSuggestion( + bot, + event + ); + } + else if (customId == "editSuggestion") + { + utils::suggestion::editSuggestion( + bot, + event + ); + } + else if (customId.starts_with("hint_button_")) + { + cmd::handleProjectHintButton( + bot, + event + ); + } + } + ); - bot.on_button_click([&bot](const dpp::button_click_t& event) { - if (event.custom_id == "delSuggestion") - utils::suggestion::deleteSuggestion(bot, event); - else if (event.custom_id == "editSuggestion") - utils::suggestion::editSuggestion(bot, event); - else if (event.custom_id.starts_with("hint_button_")) - cmd::handleProjectHintButton(bot, event); - }); + bot.on_form_submit( + [&bot](const dpp::form_submit_t& event) + { + if (event.custom_id == "editModal") + { + utils::suggestion::showSuggestionEditModal( + bot, + event + ); + } + } + ); - bot.on_form_submit([&bot](const dpp::form_submit_t& event) { - if (event.custom_id == "editModal") - utils::suggestion::showSuggestionEditModal(bot, event); - }); + std::cout + << "[*] Connecting to Discord..." + << std::endl; bot.start(dpp::st_wait); + return 0; } From 6fcbd8f0a22b6279979b71d8123d7d1fcb59d72e Mon Sep 17 00:00:00 2001 From: vexryn11 Date: Thu, 27 Aug 2026 23:33:21 -0700 Subject: [PATCH 3/7] Update main.cpp --- src/main.cpp | 252 +++++++++++++++++++++------------------------------ 1 file changed, 101 insertions(+), 151 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6c64f62..16ea930 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,33 +10,15 @@ #include "globals/globals.h" #include "utils/suggestion/suggestion.h" #include "utils/moderation/moderation.h" +#include "edit.h" using json = nlohmann::json; std::vector cmdList = { - { - "topic", - "Get a topic question", - cmd::topicCommand - }, - - { - "beginner", - "Get a beginner's guide to C++", - cmd::beginnerCommand - }, - - { - "coding", - "Get a coding question", - cmd::codingCommand - }, - - { - "close", - "Close a ticket or forum post", - cmd::closeCommand - }, + { "topic", "Get a topic question", cmd::topicCommand }, + { "beginner", "Get a beginner's guide to C++", cmd::beginnerCommand }, + { "coding", "Get a coding question", cmd::codingCommand }, + { "close", "Close a ticket or forum post", cmd::closeCommand }, { "ticket", @@ -52,17 +34,8 @@ std::vector cmdList = { } }, - { - "code", - "Formatting code on Discord", - cmd::codeCommand - }, - - { - "project", - "Get a project idea", - cmd::projectCommand - }, + { "code", "Formatting code on Discord", cmd::codeCommand }, + { "project", "Get a project idea", cmd::projectCommand }, { "rule", @@ -79,77 +52,16 @@ std::vector cmdList = { } }; - - -std::vector createSlashCommands( - const dpp::snowflake& applicationId -) -{ - std::vector commands; - - for (const auto& item : cmdList) - { - dpp::slashcommand command; - - command - .set_name(item.name) - .set_description(item.desc) - .set_application_id(applicationId); - - for (const auto& option : item.args) - { - command.add_option(option); - } - - if (item.permissions) - { - command.set_default_permissions( - dpp::permission(item.permissions) - ); - } - - commands.push_back(command); - } - - return commands; -} - - -bool executeCommand( - dpp::cluster& bot, - const dpp::slashcommand_t& event -) -{ - const std::string commandName = - event.command.get_command_name(); - - for (const auto& item : cmdList) - { - if (item.name == commandName) - { - item.function(bot, event); - return true; - } - } - - return false; -} - - int main() { std::cout << "[*] Starting bot..." << std::endl; - - + // Load config std::ifstream configFile("config.json"); if (!configFile.is_open()) { - std::cerr - << "[!] Failed to open config.json" - << std::endl; - + std::cerr << "[!] Could not open config.json" << std::endl; return 1; } @@ -159,56 +71,55 @@ int main() { configFile >> config; } - catch (const json::parse_error& error) + catch (const json::parse_error& e) { std::cerr - << "[!] Invalid JSON in config.json: " - << error.what() + << "[!] Invalid config.json: " + << e.what() << std::endl; return 1; } - - std::string globalsConfigError; - - if (!globals::loadFromConfig( - config, - globalsConfigError)) + // Check token + if (!config.contains("token") || + !config["token"].is_string() || + config["token"].get().empty()) { - std::cerr - << "[!] Invalid configuration: " - << globalsConfigError - << std::endl; - + std::cerr << "[!] Discord token is missing." << std::endl; return 1; } + // Load globals + std::string globalsConfigError; - if (!config.contains("token") || - !config["token"].is_string() || - config["token"].get().empty()) + if (!globals::loadFromConfig(config, globalsConfigError)) { std::cerr - << "[!] Discord token is missing from config.json" + << "[!] Invalid configuration: " + << globalsConfigError << std::endl; return 1; } - const std::string token = - config["token"].get(); - - - + // Create bot dpp::cluster bot( - token, + config["token"].get(), dpp::i_default_intents | dpp::i_message_content ); ModerationService moderationService(bot); + // Edit command system + edit::CommandManager editCommands; + edit::registerCommands(editCommands); + + // --------------------------------------------------------- + // READY + // --------------------------------------------------------- + bot.on_ready([&bot](const dpp::ready_t& event) { std::cout @@ -224,18 +135,38 @@ int main() ) ); - // Register slash commands once. if (dpp::run_once()) { std::cout << "[*] Registering slash commands..." << std::endl; - auto slashCommands = - createSlashCommands(bot.me.id); + std::vector slashcommands; + + for (const auto& item : cmdList) + { + dpp::slashcommand command; + + command + .set_name(item.name) + .set_description(item.desc) + .set_application_id(bot.me.id); + + for (const auto& option : item.args) + command.add_option(option); + + if (item.permissions) + { + command.set_default_permissions( + dpp::permission(item.permissions) + ); + } + + slashcommands.push_back(command); + } bot.global_bulk_command_create( - slashCommands, + slashcommands, [](const dpp::confirmation_callback_t& callback) { if (callback.is_error()) @@ -244,21 +175,24 @@ int main() << "[!] Failed to register commands: " << callback.get_error().message << std::endl; - - return; } - - std::cout - << "[+] Slash commands registered." - << std::endl; + else + { + std::cout + << "[+] Slash commands registered." + << std::endl; + } } ); } }); + // --------------------------------------------------------- + // SLASH COMMANDS + // --------------------------------------------------------- bot.on_slashcommand( - [&bot](const dpp::slashcommand_t& event) + [&bot, &editCommands](const dpp::slashcommand_t& event) { const std::string commandName = event.command.get_command_name(); @@ -266,38 +200,44 @@ int main() std::cout << "[COMMAND] /" << commandName - << " used by " - << event.command.get_issuing_user().username << std::endl; - if (!executeCommand(bot, event)) + // Handle commands from edit.cpp + if (editCommands.handleCommand(bot, event)) + return; + + // Handle original commands + for (const auto& item : cmdList) { - std::cerr - << "[!] Unknown command: /" - << commandName - << std::endl; + if (item.name == commandName) + { + item.function(bot, event); + return; + } } + + std::cerr + << "[!] Unknown command: /" + << commandName + << std::endl; } ); - + // --------------------------------------------------------- + // MESSAGE CREATE + // --------------------------------------------------------- bot.on_message_create( [&bot, &moderationService](const dpp::message_create_t& event) { - // Moderation gets first priority. if (moderationService.handleMessage(event)) - { return; - } const dpp::channel* channel = dpp::find_channel(event.msg.channel_id); if (!channel) - { return; - } if (channel->name == "suggestions") { @@ -309,28 +249,30 @@ int main() } ); + // --------------------------------------------------------- + // BUTTONS + // --------------------------------------------------------- bot.on_button_click( [&bot](const dpp::button_click_t& event) { - const std::string& customId = - event.custom_id; - - if (customId == "delSuggestion") + if (event.custom_id == "delSuggestion") { utils::suggestion::deleteSuggestion( bot, event ); } - else if (customId == "editSuggestion") + else if (event.custom_id == "editSuggestion") { utils::suggestion::editSuggestion( bot, event ); } - else if (customId.starts_with("hint_button_")) + else if ( + event.custom_id.starts_with("hint_button_") + ) { cmd::handleProjectHintButton( bot, @@ -340,6 +282,10 @@ int main() } ); + // --------------------------------------------------------- + // MODALS + // --------------------------------------------------------- + bot.on_form_submit( [&bot](const dpp::form_submit_t& event) { @@ -353,6 +299,10 @@ int main() } ); + // --------------------------------------------------------- + // START + // --------------------------------------------------------- + std::cout << "[*] Connecting to Discord..." << std::endl; From ef236077029c0743c8b80fe68578b2c24bd50cf1 Mon Sep 17 00:00:00 2001 From: vexryn11 Date: Thu, 27 Aug 2026 23:33:46 -0700 Subject: [PATCH 4/7] Update edit.cpp --- src/edit.cpp | 121 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/src/edit.cpp b/src/edit.cpp index a0385a7..a318b48 100644 --- a/src/edit.cpp +++ b/src/edit.cpp @@ -1,63 +1,94 @@ -#include -#include -#include -#include - -class Bot { -private: - std::string name; - std::unordered_map> commands; +#include "edit.h" -public: - explicit Bot(std::string botName) - : name(std::move(botName)) {} +#include - void registerCommand(const std::string& command, - std::function callback) { +namespace edit +{ + void CommandManager::registerCommand( + const std::string& command, + CommandCallback callback + ) + { commands[command] = std::move(callback); } - void handleCommand(const std::string& input) const { - auto it = commands.find(input); + bool CommandManager::handleCommand( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + const std::string commandName = + event.command.get_command_name(); + + auto it = commands.find(commandName); + + if (it == commands.end()) + return false; - if (it != commands.end()) { - it->second(); - } else { - std::cout << "Unknown command: " << input << '\n'; - } + it->second(bot, event); + + return true; } - void run() const { - std::cout << name << " is running.\n"; - std::cout << "Type a command:\n"; + // --------------------------------------------------------- + // /ping + // --------------------------------------------------------- - std::string input; + void ping( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + event.reply("🏓 Pong!"); + } - while (std::cout << "> " && std::getline(std::cin, input)) { - if (input == "exit") - break; + // --------------------------------------------------------- + // /hello + // --------------------------------------------------------- - handleCommand(input); - } + void hello( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + event.reply("Hello! 👋"); } -}; -int main() { - Bot bot("Discord Bot"); + // --------------------------------------------------------- + // /status + // --------------------------------------------------------- - bot.registerCommand("ping", [] { - std::cout << "Pong!\n"; - }); + void status( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + event.reply("🟢 Bot is online."); + } - bot.registerCommand("hello", [] { - std::cout << "Hello!\n"; - }); + // --------------------------------------------------------- + // /cpp + // --------------------------------------------------------- - bot.registerCommand("status", [] { - std::cout << "Bot is online.\n"; - }); + void cpp( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + event.reply( + "C++ is a powerful compiled programming language." + ); + } - bot.run(); + // --------------------------------------------------------- + // Register commands + // --------------------------------------------------------- - return 0; -} \ No newline at end of file + void registerCommands(CommandManager& manager) + { + manager.registerCommand("ping", ping); + manager.registerCommand("hello", hello); + manager.registerCommand("status", status); + manager.registerCommand("cpp", cpp); + } +} From 9cd543a4e41038119b65a7cfe03c305c45209a75 Mon Sep 17 00:00:00 2001 From: vexryn11 Date: Thu, 27 Aug 2026 23:34:04 -0700 Subject: [PATCH 5/7] Create edit.h --- src/edit.h | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/edit.h diff --git a/src/edit.h b/src/edit.h new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/edit.h @@ -0,0 +1 @@ + From 3c0236793d9d2a7ada23953ef1d41bac3087976a Mon Sep 17 00:00:00 2001 From: vexryn11 Date: Thu, 27 Aug 2026 23:34:21 -0700 Subject: [PATCH 6/7] Update edit.h --- src/edit.h | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/edit.h b/src/edit.h index 8b13789..4510d4e 100644 --- a/src/edit.h +++ b/src/edit.h @@ -1 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace edit +{ + using CommandCallback = + std::function; + + class CommandManager + { + private: + std::unordered_map< + std::string, + CommandCallback + > commands; + + public: + void registerCommand( + const std::string& command, + CommandCallback callback + ); + + bool handleCommand( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ); + }; + + void registerCommands(CommandManager& manager); +} From da255a93af7ffac73d28fe2d7ab9440dfecc6c0a Mon Sep 17 00:00:00 2001 From: vexryn11 Date: Thu, 27 Aug 2026 23:35:25 -0700 Subject: [PATCH 7/7] Update edit.cpp --- src/edit.cpp | 901 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 880 insertions(+), 21 deletions(-) diff --git a/src/edit.cpp b/src/edit.cpp index a318b48..64829a5 100644 --- a/src/edit.cpp +++ b/src/edit.cpp @@ -1,9 +1,151 @@ #include "edit.h" -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace edit { + // ========================================================= + // Internal helpers + // ========================================================= + + namespace + { + using Clock = std::chrono::steady_clock; + + const auto startTime = Clock::now(); + + std::mt19937& rng() + { + static std::mt19937 generator{ + std::random_device{}() + }; + + return generator; + } + + std::string formatDuration( + std::chrono::seconds seconds + ) + { + long long total = seconds.count(); + + const long long days = total / 86400; + total %= 86400; + + const long long hours = total / 3600; + total %= 3600; + + const long long minutes = total / 60; + const long long secs = total % 60; + + std::ostringstream output; + + if (days > 0) + output << days << "d "; + + if (hours > 0 || days > 0) + output << hours << "h "; + + if (minutes > 0 || hours > 0 || days > 0) + output << minutes << "m "; + + output << secs << "s"; + + return output.str(); + } + + std::string currentTime() + { + const std::time_t now = std::time(nullptr); + + std::tm tm{}; + +#ifdef _WIN32 + localtime_s(&tm, &now); +#else + localtime_r(&now, &tm); +#endif + + std::ostringstream output; + + output << std::put_time( + &tm, + "%Y-%m-%d %H:%M:%S" + ); + + return output.str(); + } + + dpp::embed baseEmbed( + const std::string& title, + const std::string& description + ) + { + dpp::embed embed; + + embed + .set_title(title) + .set_description(description) + .set_timestamp(time(nullptr)); + + return embed; + } + + void replyEmbed( + const dpp::slashcommand_t& event, + const dpp::embed& embed + ) + { + event.reply( + dpp::message() + .add_embed(embed) + ); + } + + bool isAdministrator( + const dpp::slashcommand_t& event + ) + { + return event.command.member.has_permission( + event.command.guild_id, + dpp::p_administrator + ); + } + + bool isModerator( + const dpp::slashcommand_t& event + ) + { + return + isAdministrator(event) || + event.command.member.has_permission( + event.command.guild_id, + dpp::p_manage_messages + ); + } + + std::string userName( + const dpp::slashcommand_t& event + ) + { + return event.command.get_issuing_user().username; + } + } + + // ========================================================= + // CommandManager + // ========================================================= + void CommandManager::registerCommand( const std::string& command, CommandCallback callback @@ -20,7 +162,7 @@ namespace edit const std::string commandName = event.command.get_command_name(); - auto it = commands.find(commandName); + const auto it = commands.find(commandName); if (it == commands.end()) return false; @@ -30,65 +172,782 @@ namespace edit return true; } - // --------------------------------------------------------- + // ========================================================= // /ping - // --------------------------------------------------------- + // ========================================================= void ping( dpp::cluster& bot, const dpp::slashcommand_t& event ) { - event.reply("🏓 Pong!"); + event.reply( + dpp::message("🏓 Pong!") + .set_flags(dpp::m_ephemeral) + ); } - // --------------------------------------------------------- + // ========================================================= // /hello - // --------------------------------------------------------- + // ========================================================= void hello( dpp::cluster& bot, const dpp::slashcommand_t& event ) { - event.reply("Hello! 👋"); + const std::string name = userName(event); + + event.reply( + "Hello, **" + name + "**! 👋" + ); } - // --------------------------------------------------------- + // ========================================================= // /status - // --------------------------------------------------------- + // ========================================================= void status( dpp::cluster& bot, const dpp::slashcommand_t& event ) { - event.reply("🟢 Bot is online."); + const auto uptime = + std::chrono::duration_cast( + Clock::now() - startTime + ); + + dpp::embed embed = + baseEmbed( + "Bot Status", + "Current status of the bot." + ); + + embed.add_field( + "Status", + "🟢 Online", + true + ); + + embed.add_field( + "Uptime", + formatDuration(uptime), + true + ); + + embed.add_field( + "Time", + currentTime(), + true + ); + + replyEmbed(event, embed); } - // --------------------------------------------------------- + // ========================================================= + // /uptime + // ========================================================= + + void uptime( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + const auto elapsed = + std::chrono::duration_cast( + Clock::now() - startTime + ); + + event.reply( + "⏱️ Uptime: **" + + formatDuration(elapsed) + + "**" + ); + } + + // ========================================================= + // /about + // ========================================================= + + void about( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + dpp::embed embed = + baseEmbed( + "About the Bot", + "A C++ Discord bot powered by DPP." + ); + + embed.add_field( + "Language", + "C++", + true + ); + + embed.add_field( + "Library", + "DPP", + true + ); + + embed.add_field( + "Purpose", + "Community utilities and moderation.", + false + ); + + replyEmbed(event, embed); + } + + // ========================================================= // /cpp - // --------------------------------------------------------- + // ========================================================= void cpp( dpp::cluster& bot, const dpp::slashcommand_t& event ) { + dpp::embed embed = + baseEmbed( + "C++", + "C++ is a compiled general-purpose programming language." + ); + + embed.add_field( + "Core topics", + "Classes, templates, pointers, references, STL, RAII.", + false + ); + + embed.add_field( + "Useful STL", + "`vector`, `string`, `unordered_map`, `optional`.", + false + ); + + replyEmbed(event, embed); + } + + // ========================================================= + // /help + // ========================================================= + + void help( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + dpp::embed embed = + baseEmbed( + "Help", + "Available utility commands." + ); + + embed.add_field( + "General", + "`/ping`\n" + "`/hello`\n" + "`/status`\n" + "`/uptime`\n" + "`/about`\n" + "`/cpp`", + false + ); + + embed.add_field( + "Fun", + "`/coinflip`\n" + "`/roll`\n" + "`/8ball`\n" + "`/choose`", + false + ); + + embed.add_field( + "Server", + "`/serverinfo`\n" + "`/userinfo`\n" + "`/avatar`", + false + ); + + replyEmbed(event, embed); + } + + // ========================================================= + // /coinflip + // ========================================================= + + void coinflip( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + std::uniform_int_distribution distribution(0, 1); + + const bool heads = distribution(rng()) == 0; + + event.reply( + heads + ? "🪙 **Heads!**" + : "🪙 **Tails!**" + ); + } + + // ========================================================= + // /roll + // ========================================================= + + void roll( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + int sides = 6; + + const auto parameter = + event.get_parameter("sides"); + + if (std::holds_alternative(parameter)) + { + sides = + static_cast( + std::get(parameter) + ); + } + + sides = std::clamp(sides, 2, 1000); + + std::uniform_int_distribution distribution( + 1, + sides + ); + + const int result = distribution(rng()); + + event.reply( + "🎲 You rolled **" + + std::to_string(result) + + "** / " + + std::to_string(sides) + ); + } + + // ========================================================= + // /8ball + // ========================================================= + + void eightBall( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + static const std::vector answers = { + "Yes.", + "No.", + "Definitely.", + "Probably.", + "Maybe.", + "Ask again later.", + "The outlook is good.", + "The outlook is unclear." + }; + + std::uniform_int_distribution distribution( + 0, + answers.size() - 1 + ); + + event.reply( + "🎱 " + answers[distribution(rng())] + ); + } + + // ========================================================= + // /choose + // ========================================================= + + void choose( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + const auto first = + std::get( + event.get_parameter("first") + ); + + const auto second = + std::get( + event.get_parameter("second") + ); + + std::uniform_int_distribution distribution( + 0, + 1 + ); + + const std::string result = + distribution(rng()) == 0 + ? first + : second; + + event.reply( + "🤔 I choose **" + + result + + "**." + ); + } + + // ========================================================= + // /serverinfo + // ========================================================= + + void serverInfo( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + const dpp::guild* guild = + dpp::find_guild(event.command.guild_id); + + if (!guild) + { + event.reply( + dpp::message( + "Unable to find this server." + ).set_flags(dpp::m_ephemeral) + ); + + return; + } + + dpp::embed embed = + baseEmbed( + "Server Information", + guild->name + ); + + embed.add_field( + "Members", + std::to_string(guild->member_count), + true + ); + + embed.add_field( + "Channels", + std::to_string(guild->channels.size()), + true + ); + + embed.add_field( + "Roles", + std::to_string(guild->roles.size()), + true + ); + + embed.add_field( + "Server ID", + std::to_string(guild->id), + false + ); + + replyEmbed(event, embed); + } + + // ========================================================= + // /userinfo + // ========================================================= + + void userInfo( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + dpp::snowflake userId = + event.command.get_issuing_user().id; + + const auto parameter = + event.get_parameter("user"); + + if (std::holds_alternative(parameter)) + { + userId = + std::get(parameter); + } + + const dpp::user* user = + dpp::find_user(userId); + + if (!user) + { + event.reply( + dpp::message( + "Unable to find that user." + ).set_flags(dpp::m_ephemeral) + ); + + return; + } + + dpp::embed embed = + baseEmbed( + "User Information", + user->username + ); + + embed.add_field( + "Username", + user->username, + true + ); + + embed.add_field( + "ID", + std::to_string(user->id), + true + ); + + embed.add_field( + "Bot", + user->is_bot() ? "Yes" : "No", + true + ); + + embed.set_thumbnail( + user->get_avatar_url( + 256 + ) + ); + + replyEmbed(event, embed); + } + + // ========================================================= + // /avatar + // ========================================================= + + void avatar( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + const dpp::user& user = + event.command.get_issuing_user(); + + dpp::embed embed = + baseEmbed( + "Avatar", + user.username + ); + + embed.set_image( + user.get_avatar_url(1024) + ); + + replyEmbed(event, embed); + } + + // ========================================================= + // /echo + // ========================================================= + + void echo( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + const auto text = + std::get( + event.get_parameter("text") + ); + + if (text.empty()) + { + event.reply( + dpp::message( + "You need to provide some text." + ).set_flags(dpp::m_ephemeral) + ); + + return; + } + + event.reply(text); + } + + // ========================================================= + // /announce + // ========================================================= + + void announce( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + if (!isModerator(event)) + { + event.reply( + dpp::message( + "You don't have permission to use this command." + ).set_flags(dpp::m_ephemeral) + ); + + return; + } + + const auto text = + std::get( + event.get_parameter("text") + ); + + dpp::message message( + text + ); + + bot.message_create( + dpp::snowflake(event.command.channel_id), + message + ); + event.reply( - "C++ is a powerful compiled programming language." + dpp::message( + "Announcement sent." + ).set_flags(dpp::m_ephemeral) ); } - // --------------------------------------------------------- - // Register commands - // --------------------------------------------------------- + // ========================================================= + // /clear + // ========================================================= + + void clear( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + if (!isModerator(event)) + { + event.reply( + dpp::message( + "You don't have permission to use this command." + ).set_flags(dpp::m_ephemeral) + ); + + return; + } + + const auto amount = + std::get( + event.get_parameter("amount") + ); + + const int count = + static_cast( + std::clamp( + amount, + 1, + 100 + ) + ); + + event.reply( + dpp::message( + "Clear command received for " + + std::to_string(count) + + " messages." + ).set_flags(dpp::m_ephemeral) + ); + } + + // ========================================================= + // /lock + // ========================================================= + + void lock( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + if (!isModerator(event)) + { + event.reply( + dpp::message( + "You don't have permission to use this command." + ).set_flags(dpp::m_ephemeral) + ); + + return; + } + + event.reply( + dpp::message( + "🔒 Channel lock requested." + ).set_flags(dpp::m_ephemeral) + ); + } + + // ========================================================= + // /unlock + // ========================================================= + + void unlock( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + if (!isModerator(event)) + { + event.reply( + dpp::message( + "You don't have permission to use this command." + ).set_flags(dpp::m_ephemeral) + ); + + return; + } + + event.reply( + dpp::message( + "🔓 Channel unlock requested." + ).set_flags(dpp::m_ephemeral) + ); + } + + // ========================================================= + // /modcheck + // ========================================================= + + void modCheck( + dpp::cluster& bot, + const dpp::slashcommand_t& event + ) + { + if (isModerator(event)) + { + event.reply( + dpp::message( + "✅ You have moderator permissions." + ).set_flags(dpp::m_ephemeral) + ); + } + else + { + event.reply( + dpp::message( + "❌ You don't have moderator permissions." + ).set_flags(dpp::m_ephemeral) + ); + } + } + + // ========================================================= + // Register all edit.cpp commands + // ========================================================= void registerCommands(CommandManager& manager) { - manager.registerCommand("ping", ping); - manager.registerCommand("hello", hello); - manager.registerCommand("status", status); - manager.registerCommand("cpp", cpp); + manager.registerCommand( + "ping", + ping + ); + + manager.registerCommand( + "hello", + hello + ); + + manager.registerCommand( + "status", + status + ); + + manager.registerCommand( + "uptime", + uptime + ); + + manager.registerCommand( + "about", + about + ); + + manager.registerCommand( + "cpp", + cpp + ); + + manager.registerCommand( + "help", + help + ); + + manager.registerCommand( + "coinflip", + coinflip + ); + + manager.registerCommand( + "roll", + roll + ); + + manager.registerCommand( + "8ball", + eightBall + ); + + manager.registerCommand( + "choose", + choose + ); + + manager.registerCommand( + "serverinfo", + serverInfo + ); + + manager.registerCommand( + "userinfo", + userInfo + ); + + manager.registerCommand( + "avatar", + avatar + ); + + manager.registerCommand( + "echo", + echo + ); + + manager.registerCommand( + "announce", + announce + ); + + manager.registerCommand( + "clear", + clear + ); + + manager.registerCommand( + "lock", + lock + ); + + manager.registerCommand( + "unlock", + unlock + ); + + manager.registerCommand( + "modcheck", + modCheck + ); } }