From 2efe0259ec3d47809de0a47d737faa5dc465fda0 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Sep 2023 22:31:49 +0000 Subject: [PATCH 01/13] docs: add auto testing of examples --- .gitignore | 3 +- Doxyfile | 6 +- docpages/example_code/CMakeLists.txt | 16 +++++ docpages/example_code/embeds.cpp | 56 ++++++++++++++++++ .../example_programs/the_basics/embeds.md | 59 +------------------ 5 files changed, 79 insertions(+), 61 deletions(-) create mode 100644 docpages/example_code/CMakeLists.txt create mode 100644 docpages/example_code/embeds.cpp diff --git a/.gitignore b/.gitignore index b641ee933f..21df953333 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ build buildtools/composer.phar src/build cmake-build-debug +docpages/example_code/build # tests test @@ -28,4 +29,4 @@ core config.json .misspell-fixer.ignore compile_commands.json -src/dpp/dpp.rc \ No newline at end of file +src/dpp/dpp.rc diff --git a/Doxyfile b/Doxyfile index 18dbbc539c..e0098049d0 100644 --- a/Doxyfile +++ b/Doxyfile @@ -790,7 +790,8 @@ EXCLUDE = deps \ build \ include/dpp/format \ include/dpp/nlohmann \ - docpages/include + docpages/include \ + docpages/example_code # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -830,7 +831,8 @@ EXCLUDE_SYMBOLS = nlohmann::* \ # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH =docpages/include +EXAMPLE_PATH = docpages/include \ + docpages/example_code # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and diff --git a/docpages/example_code/CMakeLists.txt b/docpages/example_code/CMakeLists.txt new file mode 100644 index 0000000000..e0fc2f9baf --- /dev/null +++ b/docpages/example_code/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required (VERSION 3.6) +project(documentation_tests) + +string(ASCII 27 Esc) + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Werror") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") + +file(GLOB example_list ./*.cpp) +foreach (example ${example_list}) + get_filename_component(examplename ${example} NAME) + message(STATUS "Found example '${Esc}[1;34m${examplename}${Esc}[m'") + add_executable(${examplename}_out ${example}) + target_link_libraries(${examplename}_out dl dpp) +endforeach(example) + diff --git a/docpages/example_code/embeds.cpp b/docpages/example_code/embeds.cpp new file mode 100644 index 0000000000..619d1e63ed --- /dev/null +++ b/docpages/example_code/embeds.cpp @@ -0,0 +1,56 @@ +#include + +int main() { + /* Setup the bot */ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ + if (event.command.get_command_name() == "embed") { + + /* Create an embed */ + dpp::embed embed = dpp::embed(). + set_color(dpp::colors::sti_blue). + set_title("Some name"). + set_url("https://dpp.dev/"). + set_author("Some name", "https://dpp.dev/", "https://dpp.dev/DPP-Logo.png"). + set_description("Some description here"). + set_thumbnail("https://dpp.dev/DPP-Logo.png"). + add_field( + "Regular field title", + "Some value here" + ). + add_field( + "Inline field title", + "Some value here", + true + ). + add_field( + "Inline field title", + "Some value here", + true + ). + set_image("https://dpp.dev/DPP-Logo.png"). + set_footer(dpp::embed_footer().set_text("Some footer text here").set_icon("https://dpp.dev/DPP-Logo.png")). + set_timestamp(time(0)); + + /* Create a message with the content as our new embed. */ + dpp::message msg(event.command.channel_id, embed); + + /* Reply to the user with the message, containing our embed. */ + event.reply(msg); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand("embed", "Send a test embed!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + return 0; +} diff --git a/docpages/example_programs/the_basics/embeds.md b/docpages/example_programs/the_basics/embeds.md index ca1934143c..ac27a6ec64 100644 --- a/docpages/example_programs/the_basics/embeds.md +++ b/docpages/example_programs/the_basics/embeds.md @@ -2,64 +2,7 @@ If you've been in a server and used a bot or even seen a message from a bot, you might have seen a special message type, often sent by these bots. These are called embeds! In this section, we will show how to create an embed and reply to a user, using our newly created embed! -~~~~~~~~~~{.cpp} -#include - -int main() { - /* Setup the bot */ - dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - /* Check which command they ran */ - if (event.command.get_command_name() == "embed") { - - /* Create an embed */ - dpp::embed embed = dpp::embed(). - set_color(dpp::colors::sti_blue). - set_title("Some name"). - set_url("https://dpp.dev/"). - set_author("Some name", "https://dpp.dev/", "https://dpp.dev/DPP-Logo.png"). - set_description("Some description here"). - set_thumbnail("https://dpp.dev/DPP-Logo.png"). - add_field( - "Regular field title", - "Some value here" - ). - add_field( - "Inline field title", - "Some value here", - true - ). - add_field( - "Inline field title", - "Some value here", - true - ). - set_image("https://dpp.dev/DPP-Logo.png"). - set_footer(dpp::embed_footer().set_text("Some footer text here").set_icon("https://dpp.dev/DPP-Logo.png")). - set_timestamp(time(0)); - - /* Create a message with the content as our new embed. */ - dpp::message msg(event.command.channel_id, embed); - - /* Reply to the user with the message, containing our embed. */ - event.reply(msg); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand("embed", "Send a test embed!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - return 0; -} -~~~~~~~~~~ +\include{cpp} embeds.cpp The code will send the following message. From 1e136f16a5720522fb9d502f0c3b04485b684628 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Sep 2023 22:44:32 +0000 Subject: [PATCH 02/13] docs: split out autocomplete --- docpages/example_code/autocomplete.cpp | 67 +++++++++++++++++ .../autocomplete.md | 71 +------------------ 2 files changed, 68 insertions(+), 70 deletions(-) create mode 100644 docpages/example_code/autocomplete.cpp diff --git a/docpages/example_code/autocomplete.cpp b/docpages/example_code/autocomplete.cpp new file mode 100644 index 0000000000..d0204abdb2 --- /dev/null +++ b/docpages/example_code/autocomplete.cpp @@ -0,0 +1,67 @@ +#include + +int main() +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new global command once on ready event */ + bot.global_command_create(dpp::slashcommand("blep", "Send a random adorable animal photo", bot.me.id) + .add_option( + /* If you set the auto complete setting on a command option, it will trigger the on_autocomplete + * event whenever discord needs to fill information for the choices. You cannot set any choices + * here if you set the auto complete value to true. + */ + dpp::command_option(dpp::co_string, "animal", "The type of animal").set_auto_complete(true) + ) + ); + } + }); + + /* The interaction create event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "blep") { + /* Fetch a parameter value from the command parameters */ + std::string animal = std::get(event.get_parameter("animal")); + /* Reply to the command. There is an overloaded version of this + * call that accepts a dpp::message so you can send embeds. + */ + event.reply("Blep! You chose " + animal); + } + }); + + /* The on_autocomplete event is fired whenever discord needs information to fill in a command options's choices. + * You must reply with a REST event within 500ms, so make it snappy! + */ + bot.on_autocomplete([&bot](const dpp::autocomplete_t & event) { + for (auto & opt : event.options) { + /* The option which has focused set to true is the one the user is typing in */ + if (opt.focused) { + /* In a real world usage of this function you should return values that loosely match + * opt.value, which contains what the user has typed so far. The opt.value is a variant + * and will contain the type identical to that of the slash command parameter. + * Here we can safely know it is string. + */ + std::string uservalue = std::get(opt.value); + bot.interaction_response_create(event.command.id, event.command.token, dpp::interaction_response(dpp::ir_autocomplete_reply) + .add_autocomplete_choice(dpp::command_option_choice("squids", "lots of squids")) + .add_autocomplete_choice(dpp::command_option_choice("cats", "a few cats")) + .add_autocomplete_choice(dpp::command_option_choice("dogs", "bucket of dogs")) + .add_autocomplete_choice(dpp::command_option_choice("elephants", "bottle of elephants")) + ); + bot.log(dpp::ll_debug, "Autocomplete " + opt.name + " with value '" + uservalue + "' in field " + event.name); + break; + } + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_programs/interactions_and_components/autocomplete.md b/docpages/example_programs/interactions_and_components/autocomplete.md index 87bee8b4fe..6ee9230ce6 100644 --- a/docpages/example_programs/interactions_and_components/autocomplete.md +++ b/docpages/example_programs/interactions_and_components/autocomplete.md @@ -2,73 +2,4 @@ Discord now supports sending auto completion lists for slash command choices. To use this feature you can use code such as the example below: -~~~~~~~~~~{.cpp} -#include - -int main() -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new global command once on ready event */ - bot.global_command_create(dpp::slashcommand("blep", "Send a random adorable animal photo", bot.me.id) - .add_option( - /* If you set the auto complete setting on a command option, it will trigger the on_autocomplete - * event whenever discord needs to fill information for the choices. You cannot set any choices - * here if you set the auto complete value to true. - */ - dpp::command_option(dpp::co_string, "animal", "The type of animal").set_auto_complete(true) - ) - ); - } - }); - - /* The interaction create event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "blep") { - /* Fetch a parameter value from the command parameters */ - std::string animal = std::get(event.get_parameter("animal")); - /* Reply to the command. There is an overloaded version of this - * call that accepts a dpp::message so you can send embeds. - */ - event.reply("Blep! You chose " + animal); - } - }); - - /* The on_autocomplete event is fired whenever discord needs information to fill in a command options's choices. - * You must reply with a REST event within 500ms, so make it snappy! - */ - bot.on_autocomplete([&bot](const dpp::autocomplete_t & event) { - for (auto & opt : event.options) { - /* The option which has focused set to true is the one the user is typing in */ - if (opt.focused) { - /* In a real world usage of this function you should return values that loosely match - * opt.value, which contains what the user has typed so far. The opt.value is a variant - * and will contain the type identical to that of the slash command parameter. - * Here we can safely know it is string. - */ - std::string uservalue = std::get(opt.value); - bot.interaction_response_create(event.command.id, event.command.token, dpp::interaction_response(dpp::ir_autocomplete_reply) - .add_autocomplete_choice(dpp::command_option_choice("squids", "lots of squids")) - .add_autocomplete_choice(dpp::command_option_choice("cats", "a few cats")) - .add_autocomplete_choice(dpp::command_option_choice("dogs", "bucket of dogs")) - .add_autocomplete_choice(dpp::command_option_choice("elephants", "bottle of elephants")) - ); - bot.log(dpp::ll_debug, "Autocomplete " + opt.name + " with value '" + uservalue + "' in field " + event.name); - break; - } - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ - +\include{cpp} autocomplete.cpp From a65f30732f5b9ce862eb0132163b74e19f08667d Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Sep 2023 22:48:11 +0000 Subject: [PATCH 03/13] docs: split attachments --- docpages/example_code/attachments1.cpp | 34 +++++ docpages/example_code/attachments2.cpp | 41 ++++++ docpages/example_code/attachments3.cpp | 41 ++++++ .../the_basics/attachments.md | 125 +----------------- 4 files changed, 119 insertions(+), 122 deletions(-) create mode 100644 docpages/example_code/attachments1.cpp create mode 100644 docpages/example_code/attachments2.cpp create mode 100644 docpages/example_code/attachments3.cpp diff --git a/docpages/example_code/attachments1.cpp b/docpages/example_code/attachments1.cpp new file mode 100644 index 0000000000..98294e24fd --- /dev/null +++ b/docpages/example_code/attachments1.cpp @@ -0,0 +1,34 @@ +#include + +int main() { + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ + if (event.command.get_command_name() == "file") { + + dpp::message msg(event.command.channel_id, "Hey there, I've got a new file!"); + + /* attach the file to the message */ + msg.add_file("foobar.txt", dpp::utility::read_file("path_to_your_file.txt")); + + /* Reply to the user with the message, with our file attached. */ + event.reply(msg); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand("file", "Send a message with a file attached!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/attachments2.cpp b/docpages/example_code/attachments2.cpp new file mode 100644 index 0000000000..2bef4d0cc8 --- /dev/null +++ b/docpages/example_code/attachments2.cpp @@ -0,0 +1,41 @@ +#include + +int main() { + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ + if (event.command.get_command_name() == "file") { + + /* Request the image from the URL specified and capture the event in a lambda. */ + bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [event](const dpp::http_request_completion_t & httpRequestCompletion) { + + /* Create a message */ + dpp::message msg(event.command.channel_id, "This is my new attachment:"); + + /* Attach the image to the message, only on success (Code 200). */ + if (httpRequestCompletion.status == 200) { + msg.add_file("logo.png", httpRequestCompletion.body); + } + + /* Send the message, with our attachment. */ + event.reply(msg); + }); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand("file", "Send a message with an image attached from the internet!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/attachments3.cpp b/docpages/example_code/attachments3.cpp new file mode 100644 index 0000000000..7b8b673cef --- /dev/null +++ b/docpages/example_code/attachments3.cpp @@ -0,0 +1,41 @@ +#include + +int main() { + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ + if (event.command.get_command_name() == "file") { + + /* Create a message. */ + dpp::message msg(event.command.channel_id, ""); + + /* Attach the image to the message we just created. */ + msg.add_file("image.jpg", dpp::utility::read_file("path_to_your_image.jpg")); + + /* Create an embed. */ + dpp::embed embed; + embed.set_image("attachment://image.jpg"); /* Set the image of the embed to the attached image. */ + + /* Add the embed to the message. */ + msg.add_embed(embed); + + event.reply(msg); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand("file", "Send a local image along with an embed with the image!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_programs/the_basics/attachments.md b/docpages/example_programs/the_basics/attachments.md index f25873430f..1a3668ae18 100644 --- a/docpages/example_programs/the_basics/attachments.md +++ b/docpages/example_programs/the_basics/attachments.md @@ -8,42 +8,7 @@ D++ has this helper function to read a file: dpp::utility::read_file. An example program: -~~~~~~~~~~{.cpp} -#include - -int main() { - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - /* Check which command they ran */ - if (event.command.get_command_name() == "file") { - - dpp::message msg(event.command.channel_id, "Hey there, I've got a new file!"); - - /* attach the file to the message */ - msg.add_file("foobar.txt", dpp::utility::read_file("path_to_your_file.txt")); - - /* Reply to the user with the message, with our file attached. */ - event.reply(msg); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand("file", "Send a message with a file attached!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} attachments1.cpp Attachments via an url aren't possible. But there's a workaround for. You can download the file and then attach it to the message. @@ -51,94 +16,10 @@ To make requests, D++ also has a helper function: dpp::cluster::request. The following example program shows how to request a file and attach it to a message. -~~~~~~~~~~{.cpp} -#include - -int main() { - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - /* Check which command they ran */ - if (event.command.get_command_name() == "file") { - - /* Request the image from the URL specified and capture the event in a lambda. */ - bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [event](const dpp::http_request_completion_t & httpRequestCompletion) { - - /* Create a message */ - dpp::message msg(event.command.channel_id, "This is my new attachment:"); - - /* Attach the image to the message, only on success (Code 200). */ - if (httpRequestCompletion.status == 200) { - msg.add_file("logo.png", httpRequestCompletion.body); - } - - /* Send the message, with our attachment. */ - event.reply(msg); - }); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand("file", "Send a message with an image attached from the internet!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} attachments2.cpp Here's another example of how to add a local image to an embed. Upload the image in the same message as the embed and then reference it in the embed. -~~~~~~~~~~{.cpp} -#include - -int main() { - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - /* Check which command they ran */ - if (event.command.get_command_name() == "file") { - - /* Create a message. */ - dpp::message msg(event.command.channel_id, ""); - - /* Attach the image to the message we just created. */ - msg.add_file("image.jpg", dpp::utility::read_file("path_to_your_image.jpg")); - - /* Create an embed. */ - dpp::embed embed; - embed.set_image("attachment://image.jpg"); /* Set the image of the embed to the attached image. */ - - /* Add the embed to the message. */ - msg.add_embed(embed); - - event.reply(msg); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand("file", "Send a local image along with an embed with the image!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} attachments3.cpp From 31afd693f803024a29bf060d14cf6051a2e3fcb2 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Sep 2023 22:58:40 +0000 Subject: [PATCH 04/13] docs: firstbot (6 examples) and webhooks --- docpages/example_code/firstbot.cpp | 25 ++++ docpages/example_code/firstbot1.cpp | 4 + docpages/example_code/firstbot2.cpp | 7 + docpages/example_code/firstbot3.cpp | 13 ++ docpages/example_code/firstbot4.cpp | 16 +++ docpages/example_code/firstbot5.cpp | 20 +++ docpages/example_code/firstbot6.cpp | 23 +++ docpages/example_code/webhooks.cpp | 16 +++ .../example_programs/the_basics/firstbot.md | 133 ++---------------- .../example_programs/the_basics/webhooks.md | 19 +-- 10 files changed, 133 insertions(+), 143 deletions(-) create mode 100644 docpages/example_code/firstbot.cpp create mode 100644 docpages/example_code/firstbot1.cpp create mode 100644 docpages/example_code/firstbot2.cpp create mode 100644 docpages/example_code/firstbot3.cpp create mode 100644 docpages/example_code/firstbot4.cpp create mode 100644 docpages/example_code/firstbot5.cpp create mode 100644 docpages/example_code/firstbot6.cpp create mode 100644 docpages/example_code/webhooks.cpp diff --git a/docpages/example_code/firstbot.cpp b/docpages/example_code/firstbot.cpp new file mode 100644 index 0000000000..fd7bb43957 --- /dev/null +++ b/docpages/example_code/firstbot.cpp @@ -0,0 +1,25 @@ +#include + +const std::string BOT_TOKEN = "add your token here"; + +int main() { + dpp::cluster bot(BOT_TOKEN); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_slashcommand([](const dpp::slashcommand_t& event) { + if (event.command.get_command_name() == "ping") { + event.reply("Pong!"); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + bot.global_command_create( + dpp::slashcommand("ping", "Ping pong!", bot.me.id) + ); + } + }); + + bot.start(dpp::st_wait); +} diff --git a/docpages/example_code/firstbot1.cpp b/docpages/example_code/firstbot1.cpp new file mode 100644 index 0000000000..321e3015b4 --- /dev/null +++ b/docpages/example_code/firstbot1.cpp @@ -0,0 +1,4 @@ +#include + +int main() { +} diff --git a/docpages/example_code/firstbot2.cpp b/docpages/example_code/firstbot2.cpp new file mode 100644 index 0000000000..5b00e5526b --- /dev/null +++ b/docpages/example_code/firstbot2.cpp @@ -0,0 +1,7 @@ +#include + +const std::string BOT_TOKEN = "add your token here"; + +int main() { + dpp::cluster bot(BOT_TOKEN); +} diff --git a/docpages/example_code/firstbot3.cpp b/docpages/example_code/firstbot3.cpp new file mode 100644 index 0000000000..ad7ee22099 --- /dev/null +++ b/docpages/example_code/firstbot3.cpp @@ -0,0 +1,13 @@ +#include + +const std::string BOT_TOKEN = "add your token here"; + +int main() { + dpp::cluster bot(BOT_TOKEN); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); + } + }); +} diff --git a/docpages/example_code/firstbot4.cpp b/docpages/example_code/firstbot4.cpp new file mode 100644 index 0000000000..cbd55f1860 --- /dev/null +++ b/docpages/example_code/firstbot4.cpp @@ -0,0 +1,16 @@ +#include + +const std::string BOT_TOKEN = "add your token here"; + +int main() { + dpp::cluster bot(BOT_TOKEN); + + bot.on_slashcommand([](const dpp::slashcommand_t& event) { + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); + } + }); +} diff --git a/docpages/example_code/firstbot5.cpp b/docpages/example_code/firstbot5.cpp new file mode 100644 index 0000000000..279a32bbe6 --- /dev/null +++ b/docpages/example_code/firstbot5.cpp @@ -0,0 +1,20 @@ +#include + +const std::string BOT_TOKEN = "add your token here"; + +int main() { + dpp::cluster bot(BOT_TOKEN); + + bot.on_slashcommand([](const dpp::slashcommand_t& event) { + if (event.command.get_command_name() == "ping") { + event.reply("Pong!"); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); + } + }); + +} diff --git a/docpages/example_code/firstbot6.cpp b/docpages/example_code/firstbot6.cpp new file mode 100644 index 0000000000..6a626ff774 --- /dev/null +++ b/docpages/example_code/firstbot6.cpp @@ -0,0 +1,23 @@ +#include + +const std::string BOT_TOKEN = "add your token here"; + +int main() { + dpp::cluster bot(BOT_TOKEN); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_slashcommand([](const dpp::slashcommand_t& event) { + if (event.command.get_command_name() == "ping") { + event.reply("Pong!"); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); +} diff --git a/docpages/example_code/webhooks.cpp b/docpages/example_code/webhooks.cpp new file mode 100644 index 0000000000..f35452328c --- /dev/null +++ b/docpages/example_code/webhooks.cpp @@ -0,0 +1,16 @@ +#include + +int main() +{ + dpp::cluster bot(""); // normally, you put your bot token in here. But to just run a webhook its not required + + bot.on_log(dpp::utility::cout_logger()); + + /* construct a webhook object using the URL you got from Discord */ + dpp::webhook wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); + + /* send a message with this webhook */ + bot.execute_webhook_sync(wh, dpp::message("Have a great time here :smile:")); + + return 0; +} diff --git a/docpages/example_programs/the_basics/firstbot.md b/docpages/example_programs/the_basics/firstbot.md index d80ca5ebad..76037907c5 100644 --- a/docpages/example_programs/the_basics/firstbot.md +++ b/docpages/example_programs/the_basics/firstbot.md @@ -12,41 +12,13 @@ The two programs can be seen side by side below: - -~~~~~~~~~~~~~~~{.cpp} -#include - -const std::string BOT_TOKEN = "add your token here"; - -int main() { - dpp::cluster bot(BOT_TOKEN); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_slashcommand([](const dpp::slashcommand_t& event) { - if (event.command.get_command_name() == "ping") { - event.reply("Pong!"); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - bot.global_command_create( - dpp::slashcommand("ping", "Ping pong!", bot.me.id) - ); - } - }); - - bot.start(dpp::st_wait); -} -~~~~~~~~~~~~~~~ - +\include{cpp} firstbot.cpp -~~~~~~~~~~~~~~~{.cpp} +~~~~~~~~~~~~~~~{.js} let Discord = require('discord.js'); @@ -85,12 +57,7 @@ Let's break this program down step by step: Make sure to include the header file for the D++ library with the instruction \#include ``! -~~~~~~~~~~~~~~{.cpp} -#include - -int main() { -} -~~~~~~~~~~~~~~ +\include{cpp} firstbot1.cpp ### 2. Create an instance of dpp::cluster @@ -98,15 +65,7 @@ To make use of the library you must create a dpp::cluster object. This object is You can instantiate this class as shown below. Remember to put your bot token in the constant! -~~~~~~~~~~~~~~~{.cpp} -#include - -const std::string BOT_TOKEN = "add your token here"; - -int main() { - dpp::cluster bot(BOT_TOKEN); -} -~~~~~~~~~~~~~~~ +\include{cpp} firstbot2.cpp ### 3. Attach to an event @@ -114,71 +73,19 @@ To have a bot that does something, you should attach to some events. Let's start command called 'ping'. Note that we must wrap our registration of the command in a template called dpp::run_once which prevents it from being re-run every time your bot does a full reconnection (e.g. if the connection fails). -~~~~~~~~~~~~~~~~{.cpp} -#include - -const std::string BOT_TOKEN = "add your token here"; - -int main() { - dpp::cluster bot(BOT_TOKEN); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); - } - }); -} -~~~~~~~~~~~~~~~~ +\include{cpp} firstbot3.cpp ### 4. Attach to another event to receive slash commands If you want to handle a slash command, you should also attach your program to the `on_slashcommand` event (dpp::cluster::on_slashcommand) which is basically the same as the Discord.js `interactionCreate` event. Lets add this to the program before the `on_ready` event: -~~~~~~~~~~~~~~{.cpp} -#include - -const std::string BOT_TOKEN = "add your token here"; - -int main() { - dpp::cluster bot(BOT_TOKEN); - - bot.on_slashcommand([](const dpp::slashcommand_t& event) { - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); - } - }); -} -~~~~~~~~~~~~~~ +\include{cpp} firstbot4.cpp ### 5 . Add some content to the events Attaching to an event is a good start, but to make a bot you should actually put some program code into the interaction event. We will add some code to the `on_slashcommand` to look for our slash command '/ping' and reply with `Pong!`: -~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -const std::string BOT_TOKEN = "add your token here"; - -int main() { - dpp::cluster bot(BOT_TOKEN); - - bot.on_slashcommand([](const dpp::slashcommand_t& event) { - if (event.command.get_command_name() == "ping") { - event.reply("Pong!"); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); - } - }); - -} -~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} firstbot5.cpp Let's break down the code in the `on_slashcommand` event so that we can discuss what it is doing: @@ -202,31 +109,7 @@ We also add a line to tell the library to output all its log information to the The parameter which we set to false indicates if the function should return once all shards are created. Passing `false` here tells the program you do not need to do anything once `bot.start` is called. -~~~~~~~~~~~~~~{.cpp} -#include - -const std::string BOT_TOKEN = "add your token here"; - -int main() { - dpp::cluster bot(BOT_TOKEN); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_slashcommand([](const dpp::slashcommand_t& event) { - if (event.command.get_command_name() == "ping") { - event.reply("Pong!"); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); -} -~~~~~~~~~~~~~~ +\include{cpp} firstbot6.cpp ### 7. Compile and run your bot diff --git a/docpages/example_programs/the_basics/webhooks.md b/docpages/example_programs/the_basics/webhooks.md index 02e1c8c438..e121c2169e 100644 --- a/docpages/example_programs/the_basics/webhooks.md +++ b/docpages/example_programs/the_basics/webhooks.md @@ -4,23 +4,6 @@ Webhooks are a simple way to post messages from other apps and websites into Dis The following code shows how to send messages in a channel using a webhook. -~~~~~~~~~~{.cpp} -#include - -int main() -{ - dpp::cluster bot(""); // normally, you put your bot token in here. But to just run a webhook its not required - - bot.on_log(dpp::utility::cout_logger()); - - /* construct a webhook object using the URL you got from Discord */ - dpp::webhook wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); - - /* send a message with this webhook */ - bot.execute_webhook_sync(wh, dpp::message("Have a great time here :smile:")); - - return 0; -} -~~~~~~~~~~ +\include{cpp} webhooks.cpp The above is just a very simple example. You can also send embed messages. All you have to do is to add an embed to the message you want to send. If you want to, you can also send it into a thread. From acfd7e413b46535977d7f08c3a4b799430a7bb18 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Sep 2023 23:40:52 +0000 Subject: [PATCH 05/13] docs: convert tons more examples and fix some --- docpages/example_code/CMakeLists.txt | 37 +++- docpages/example_code/callbacks.cpp | 88 ++++++++ .../example_code/coro_awaiting_events.cpp | 39 ++++ .../example_code/coro_expiring_buttons.cpp | 43 ++++ docpages/example_code/coro_intro.cpp | 35 ++++ .../example_code/coro_simple_commands1.cpp | 63 ++++++ .../example_code/coro_simple_commands2.cpp | 82 ++++++++ docpages/example_code/editing_messages.cpp | 73 +++++++ docpages/example_code/eval.cpp | 175 ++++++++++++++++ docpages/example_code/eval.h | 14 ++ docpages/example_code/join_voice.cpp | 87 ++++++++ docpages/example_code/mp3.cpp | 117 +++++++++++ docpages/example_code/oggopus.cpp | 117 +++++++++++ docpages/example_code/record_user.cpp | 72 +++++++ docpages/example_code/soundboard.cpp | 85 ++++++++ docpages/example_programs/misc/eval.md | 195 +----------------- .../music_and_audio/join_voice.md | 91 +------- .../example_programs/music_and_audio/mp3.md | 122 +---------- .../music_and_audio/oggopus.md | 120 +---------- .../music_and_audio/record_user.md | 76 +------ .../music_and_audio/soundboard.md | 88 +------- .../editing-channels-and-messages.md | 76 +------ .../the_basics/using_callback_functions.md | 91 +------- .../using_coroutines/awaiting_events.md | 42 +--- .../using_coroutines/coro_introduction.md | 39 +--- .../using_coroutines/coro_simple_commands.md | 151 +------------- .../using_coroutines/expiring_buttons.md | 46 +---- 27 files changed, 1137 insertions(+), 1127 deletions(-) create mode 100644 docpages/example_code/callbacks.cpp create mode 100644 docpages/example_code/coro_awaiting_events.cpp create mode 100644 docpages/example_code/coro_expiring_buttons.cpp create mode 100644 docpages/example_code/coro_intro.cpp create mode 100644 docpages/example_code/coro_simple_commands1.cpp create mode 100644 docpages/example_code/coro_simple_commands2.cpp create mode 100644 docpages/example_code/editing_messages.cpp create mode 100644 docpages/example_code/eval.cpp create mode 100644 docpages/example_code/eval.h create mode 100644 docpages/example_code/join_voice.cpp create mode 100644 docpages/example_code/mp3.cpp create mode 100644 docpages/example_code/oggopus.cpp create mode 100644 docpages/example_code/record_user.cpp create mode 100644 docpages/example_code/soundboard.cpp diff --git a/docpages/example_code/CMakeLists.txt b/docpages/example_code/CMakeLists.txt index e0fc2f9baf..a5d1309729 100644 --- a/docpages/example_code/CMakeLists.txt +++ b/docpages/example_code/CMakeLists.txt @@ -1,9 +1,39 @@ -cmake_minimum_required (VERSION 3.6) +# +# D++ (DPP), The Lightweight C++ Discord Library +# +# Copyright 2021 Craig Edwards +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example programs test compilation +# This build script is executed by a GitHub action to ensure all example +# programs compile correctly. It does not attempt to run them, as there +# is no way to know if the program successfully did its thing, plus +# examples do not have a valid token. This build script assumes the +# following system dependencies are available: +# +# g++-12 or later +# liboggz-dev +# libmpg123-dev +# dpp latest master with -DDPP_CORO=ON installed sytemwide + +cmake_minimum_required (VERSION 3.16) project(documentation_tests) string(ASCII 27 Esc) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Werror") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Werror") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") file(GLOB example_list ./*.cpp) @@ -11,6 +41,5 @@ foreach (example ${example_list}) get_filename_component(examplename ${example} NAME) message(STATUS "Found example '${Esc}[1;34m${examplename}${Esc}[m'") add_executable(${examplename}_out ${example}) - target_link_libraries(${examplename}_out dl dpp) + target_link_libraries(${examplename}_out dl dpp mpg123 oggz) endforeach(example) - diff --git a/docpages/example_code/callbacks.cpp b/docpages/example_code/callbacks.cpp new file mode 100644 index 0000000000..fc5d236cd5 --- /dev/null +++ b/docpages/example_code/callbacks.cpp @@ -0,0 +1,88 @@ +#include + +int main() { + dpp::cluster bot("Token Was Here", dpp::i_default_intents | dpp::i_message_content); + /* the second argument is a bitmask of intents - i_message_content is needed to get messages */ + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) -> void { + if (event.command.get_command_name() == "msgs-get") { + int64_t limit = std::get(event.get_parameter("quantity")); + + /* get messages using ID of the channel the command was issued in */ + bot.messages_get(event.command.channel_id, 0, 0, 0, limit, [event](const dpp::confirmation_callback_t& callback) -> void { + if (callback.is_error()) { /* catching an error to log it */ + std::cout << callback.get_error().message << std::endl; + return; + } + + auto messages = callback.get(); + /* std::get(callback.value) would give the same result */ + + std::string contents; + for (const auto& x : messages) { /* here we iterate through the dpp::message_map we got from callback... */ + contents += x.second.content + '\n'; /* ...where x.first is ID of the current message and x.second is the message itself. */ + } + + event.reply(contents); /* we will see all those messages we got, united as one! */ + }); + } else if (event.command.get_command_name() == "channel-create") { + /* create a text channel */ + dpp::channel channel = dpp::channel() + .set_name("test") + .set_guild_id(event.command.guild_id); + + bot.channel_create(channel, [&bot, event](const dpp::confirmation_callback_t& callback) -> void { + if (callback.is_error()) { /* catching an error to log it */ + bot.log(dpp::loglevel::ll_error, callback.get_error().message); + return; + } + + auto channel = callback.get(); + /* std::get(callback.value) would give the same result */ + + /* reply with the created channel information */ + dpp::message message = dpp::message("The channel's name is `" + channel.name + "`, ID is `" + std::to_string(channel.id) + " and type is `" + std::to_string(channel.get_type()) + "`."); + /* note that channel types are represented as numbers */ + event.reply(message); + }); + } else if (event.command.get_command_name() == "msg-error") { + bot.message_get(0, 0, [event](const dpp::confirmation_callback_t& callback) -> void { + /* the error will occur since there is no message with ID '0' that is in a channel with ID '0' (I'm not explaining why) */ + if (callback.is_error()) { + event.reply(callback.get_error().message); + return; + } + + /* we won't be able to get here because of the return; statement */ + auto message = callback.get(); + event.reply(message); + }); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once ()) { + dpp::slashcommand msgs_get("msgs-get", "Get messages", bot.me.id); + + constexpr int64_t min_val{1}; + constexpr int64_t max_val{100}; + + msgs_get.add_option( + dpp::command_option(dpp::co_integer, "quantity", "Quantity of messages to get. Max - 100.") + .set_min_value(min_val) + .set_max_value(max_val) + ); + + dpp::slashcommand channel_create("channel-create", "Create a channel", bot.me.id); + dpp::slashcommand msg_error("msg-error", "Get an error instead of message :)", bot.me.id); + + bot.global_bulk_command_create({ msgs_get, channel_create, msg_error }); + } + }); + + bot.start(dpp::st_wait); + return 0; +} diff --git a/docpages/example_code/coro_awaiting_events.cpp b/docpages/example_code/coro_awaiting_events.cpp new file mode 100644 index 0000000000..3f62f27e9d --- /dev/null +++ b/docpages/example_code/coro_awaiting_events.cpp @@ -0,0 +1,39 @@ +#include + +int main() { + dpp::cluster bot{"token"}; + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { + if (event.command.get_command_name() == "test") { + // Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it + dpp::message m{"Test"}; + std::string id{event.command.id.str()}; + m.add_component( + dpp::component{}.add_component(dpp::component{}.set_type(dpp::cot_button).set_label("Click me!").set_id(id)) + ); + co_await event.co_reply(m); + + dpp::button_click_t click_event = co_await event.from->creator->on_button_click.when( + // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference here or the compiler will destroy it twice. This is fixed in g++13 + [&id] (dpp::button_click_t const &b) { + return b.custom_id == id; + } + ); + // Acknowledge the click and edit the original response, removing the button + click_event.reply(); + event.edit_original_response(dpp::message{"You clicked the button!"}); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id}; + + bot.global_command_create(command); + } + }); + + bot.start(dpp::st_wait); +} diff --git a/docpages/example_code/coro_expiring_buttons.cpp b/docpages/example_code/coro_expiring_buttons.cpp new file mode 100644 index 0000000000..01a7958356 --- /dev/null +++ b/docpages/example_code/coro_expiring_buttons.cpp @@ -0,0 +1,43 @@ +#include + +int main() { + dpp::cluster bot{"token"}; + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { + if (event.command.get_command_name() == "test") { + // Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it + dpp::message m{"Test"}; + std::string id{event.command.id.str()}; + m.add_component( + dpp::component{}.add_component(dpp::component{}.set_type(dpp::cot_button).set_label("Click me!").set_id(id)) + ); + co_await event.co_reply(m); + + auto result = co_await dpp::when_any{ // Whichever completes first... + event.from->creator->on_button_click.when([&id](const dpp::button_click_t &b) { return b.custom_id == id; }), // Button clicked + event.from->creator->co_sleep(5) // Or sleep 5 seconds + }; + // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference above or the compiler will destroy it twice. This is fixed in g++13 + if (result.index() == 0) { // Awaitable #0 completed first, that is the button click event + // Acknowledge the click and edit the original response, removing the button + const dpp::button_click_t &click_event = result.get<0>(); + click_event.reply(); + event.edit_original_response(dpp::message{"You clicked the button with the id " + click_event.custom_id}); + } else { // Here index() is 1, the timer expired + event.edit_original_response(dpp::message{"I haven't got all day!"}); + } + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id}; + + bot.global_command_create(command); + } + }); + + bot.start(dpp::st_wait); +} diff --git a/docpages/example_code/coro_intro.cpp b/docpages/example_code/coro_intro.cpp new file mode 100644 index 0000000000..d4177c7f02 --- /dev/null +++ b/docpages/example_code/coro_intro.cpp @@ -0,0 +1,35 @@ +#include + +int main() { + dpp::cluster bot{"token"}; + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + /* Make note of passing the event by value, this is important (explained below) */ + bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { + if (event.command.get_command_name() == "file") { + /* Request the image from the URL specified and co_await the response */ + dpp::http_request_completion_t result = co_await event.from->creator->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get); + + /* Create a message and attach the image on success */ + dpp::message msg(event.command.channel_id, "This is my new attachment:"); + if (result.status == 200) { + msg.add_file("logo.png", result.body); + } + + /* Send the message, with our attachment. */ + event.reply(msg); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand{"file", "Send a message with an image attached from the internet!", bot.me.id}); + } + }); + + bot.start(dpp::st_wait); + return 0; +} diff --git a/docpages/example_code/coro_simple_commands1.cpp b/docpages/example_code/coro_simple_commands1.cpp new file mode 100644 index 0000000000..8eb8b90dbc --- /dev/null +++ b/docpages/example_code/coro_simple_commands1.cpp @@ -0,0 +1,63 @@ +#include + +int main() { + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { + if (event.command.get_command_name() == "addemoji") { + dpp::cluster *cluster = event.from->creator; + // Retrieve parameter values + dpp::snowflake file_id = std::get(event.get_parameter("file")); + std::string emoji_name = std::get(event.get_parameter("name")); + + // Get the attachment from the resolved list + const dpp::attachment &attachment = event.command.get_resolved_attachment(file_id); + + // For simplicity for this example we only support PNG + if (attachment.content_type != "image/png") { + // While we could use event.co_reply, we can just use event.reply, as we will exit the command anyway and don't need to wait on the result + event.reply("Error: type " + attachment.content_type + " not supported"); + co_return; + } + // Send a " is thinking..." message, to wait on later so we can edit + dpp::async thinking = event.co_thinking(false); + + // Download and co_await the result + dpp::http_request_completion_t response = co_await cluster->co_request(attachment.url, dpp::m_get); + + if (response.status != 200) { // Page didn't send the image + co_await thinking; // Wait for the thinking response to arrive so we can edit + event.edit_response("Error: could not download the attachment"); + } else { + // Load the image data in a dpp::emoji + dpp::emoji emoji(emoji_name); + emoji.load_image(response.body, dpp::image_type::i_png); + + // Create the emoji and co_await the response + dpp::confirmation_callback_t confirmation = co_await cluster->co_guild_emoji_create(event.command.guild_id, emoji); + + co_await thinking; // Wait for the thinking response to arrive so we can edit + if (confirmation.is_error()) { + event.edit_response("Error: could not add emoji: " + confirmation.get_error().message); + } else { // Success + event.edit_response("Successfully added " + confirmation.get().get_mention()); // Show the new emoji + } + } + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + dpp::slashcommand command("addemoji", "Add an emoji", bot.me.id); + // Add file and name as required parameters + command.add_option(dpp::command_option(dpp::co_attachment, "file", "Select an image", true)); + command.add_option(dpp::command_option(dpp::co_string, "name", "Name of the emoji to add", true)); + + bot.global_command_create(command); + } + }); + + bot.start(dpp::st_wait); +} diff --git a/docpages/example_code/coro_simple_commands2.cpp b/docpages/example_code/coro_simple_commands2.cpp new file mode 100644 index 0000000000..5858286eed --- /dev/null +++ b/docpages/example_code/coro_simple_commands2.cpp @@ -0,0 +1,82 @@ +#include + +int main() { + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { + if (event.command.get_command_name() == "avatar") { + // Make a nested coroutine to fetch the guild member requested, that returns it as an optional + constexpr auto resolve_member = [](const dpp::slashcommand_t &event) -> dpp::task> { + const dpp::command_value &user_param = event.get_parameter("user"); + dpp::snowflake user_id; + if (std::holds_alternative(user_param)) { + user_id = event.command.usr.id; // Parameter is empty so user is sender + } + else if (std::holds_alternative(user_param)) { + user_id = std::get(user_param); // Parameter has a user + } + + // If we have the guild member in the command's resolved data, return it + const auto &member_map = event.command.resolved.members; + if (auto member = member_map.find(user_id); member != member_map.end()) + co_return member->second; + // Try looking in guild cache + dpp::guild *guild = dpp::find_guild(event.command.guild_id); + if (guild) { + // Look in guild's member cache + if (auto member = guild->members.find(user_id); member != guild->members.end()) { + co_return member->second; + } + } + // Finally if everything else failed, request API + dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_guild_get_member(event.command.guild_id, user_id); + if (confirmation.is_error()) { + co_return std::nullopt; // Member not found, return empty + } else { + co_return confirmation.get(); + } + }; + + // Send a " is thinking..." message, to wait on later so we can edit + dpp::async thinking = event.co_thinking(false); + // Call our coroutine defined above to retrieve the member requested + std::optional member = co_await resolve_member(event); + if (!member.has_value()) { + // Wait for the thinking response to arrive to make sure we can edit + co_await thinking; + event.edit_original_response(dpp::message{"User not found in this server!"}); + co_return; + } + + std::string avatar_url = member->get_avatar_url(512); + if (avatar_url.empty()) { // Member does not have a custom avatar for this server, get their user avatar + dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_user_get_cached(member->user_id); + if (confirmation.is_error()) { + // Wait for the thinking response to arrive to make sure we can edit + co_await thinking; + event.edit_original_response(dpp::message{"User not found!"}); + co_return; + } + avatar_url = confirmation.get().get_avatar_url(512); + } + + // Wait for the thinking response to arrive to make sure we can edit + co_await thinking; + event.edit_original_response(dpp::message{avatar_url}); + } + }); + + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + dpp::slashcommand command("avatar", "Get your or another user's avatar image", bot.me.id); + command.add_option(dpp::command_option(dpp::co_user, "user", "User to fetch the avatar from")); + + bot.global_command_create(command); + } + }); + + bot.start(dpp::st_wait); +} diff --git a/docpages/example_code/editing_messages.cpp b/docpages/example_code/editing_messages.cpp new file mode 100644 index 0000000000..5bca1afe85 --- /dev/null +++ b/docpages/example_code/editing_messages.cpp @@ -0,0 +1,73 @@ +#include + +int main() { + dpp::cluster bot("Token", dpp::i_default_intents | dpp::i_message_content); + /* the second argument is a bitmask of intents - i_message_content is needed to get messages */ + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + if (event.command.get_command_name() == "msg-send") { + event.reply("That's a message"); + } else if (event.command.get_command_name() == "msg-edit") { + const auto content = std::get(event.get_parameter("content")); + + /* get message to edit it after */ + const dpp::snowflake msg_id = std::get(event.get_parameter("msg-id")); + /* here string will automatically be converted to snowflake */ + + bot.message_get(msg_id, event.command.channel_id, [&bot, content, event](const dpp::confirmation_callback_t& callback) { + if (callback.is_error()) { + event.reply("error"); + return; + } + auto message = callback.get(); + + /* change the message content and edit the message itself */ + message.set_content(content); + bot.message_edit(message); + event.reply("Message content is now `" + content + "`."); + }); + } else if (event.command.get_command_name() == "channel-edit") { + const auto name = std::get(event.get_parameter("name")); + + /* get the channel to edit it after */ + const auto channel_id = std::get(event.get_parameter("channel")); + bot.channel_get(channel_id, [&bot, name, event](const dpp::confirmation_callback_t& callback) { + if (callback.is_error()) { + event.reply("error"); + return; + } + auto channel = callback.get(); + + /* change the channel name and edit the channel itself */ + channel.set_name(name); + bot.channel_edit(channel); + event.reply("Channel name is now `" + name + "`."); + }); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + + if (dpp::run_once ()) { + dpp::slashcommand msg_edit("msg-edit", "Edit a message sent by the bot", bot.me.id); + + msg_edit.add_option(dpp::command_option(dpp::co_string, "msg-id", "ID of the message to edit", true)); /* true for required option */ + msg_edit.add_option(dpp::command_option(dpp::co_string, "content", "New content for the message", true)); /* same here */ + + dpp::slashcommand channel_edit("channel-edit", "Edit the name of channel specified", bot.me.id); + + channel_edit.add_option(dpp::command_option(dpp::co_channel, "channel", "Channel to edit", true)); + channel_edit.add_option(dpp::command_option(dpp::co_string, "name", "New name for the channel", true)); + + dpp::slashcommand msg_send("msg-send", "Send my message", bot.me.id); + + bot.global_bulk_command_create({ msg_edit, channel_edit, msg_send }); + } + }); + + bot.start(dpp::st_wait); + return 0; +} diff --git a/docpages/example_code/eval.cpp b/docpages/example_code/eval.cpp new file mode 100644 index 0000000000..46af646ce4 --- /dev/null +++ b/docpages/example_code/eval.cpp @@ -0,0 +1,175 @@ +/** + * D++ eval command example. + * This is dangerous and for educational use only, here be dragons! + */ + +#include +#include +#include +#include +/* We have to define this to make certain functions visible */ +#ifndef _GNU_SOURCE + #define _GNU_SOURCE +#endif +#include +#include +#include "eval.h" + +/* This is an example function you can expose to your eval command */ +int test_function() { + return 42; +} + +/* Important: This code is for UNIX-like systems only, e.g. + * Linux, BSD, OSX. It will NOT work on Windows! + * Note for OSX you'll probably have to change all references + * from .so to .dylib. + */ +int main() +{ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); + + bot.on_log(dpp::utility::cout_logger()); + + /* This won't work in a slash command very well yet, as there is not yet + * a multi-line slash command input type. + */ + bot.on_message_create([&bot](const auto & event) { + if (dpp::utility::utf8substr(event.msg.content, 0, 5) == "!eval") { + + /** + * THIS IS CRITICALLY IMPORTANT! + * Never EVER make an eval command that isn't restricted to a specific developer by user id. + * With access to this command the person who invokes it has at best full control over + * your bot's user account and at worst, full control over your entire network!!! + * Eval commands are Evil (pun intended) and could even be considered a security + * vulnerability. YOU HAVE BEEN WARNED! + */ + if (event.msg.author.id != dpp::snowflake(MY_DEVELOPER)) { + bot.message_create(dpp::message(event.msg.channel_id, "On the day i do this for you, Satan will be ice skating to work.")); + return; + } + + /* We start by creating a string that contains a cpp program for a simple library. + * The library will contain one exported function called so_exec() that is called + * containing the raw C++ code to eval. + */ + std::string code = "#include \n\ + #include \n\ + #include \n\ + #include \n\ + #include \n\ + #include \n\ + #include \n\ + #include \n\ + #include \"eval.h\"\n\ + extern \"C\" void so_exec(dpp::cluster& bot, dpp::message_create_t event) {\n\ + " + dpp::utility::utf8substr( + event.msg.content, + 6, + dpp::utility::utf8len(event.msg.content) + ) + ";\n\ + return;\n\ + }"; + + /* Next we output this string full of C++ to a cpp file on disk. + * This code assumes the current directory is writeable. The file will have a + * unique name made from the user's id and the message id. + */ + std::string source_filename = std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp"; + std::fstream code_file(source_filename, std::fstream::binary | std::fstream::out); + if (!code_file.is_open()) { + bot.message_create(dpp::message(event.msg.channel_id, "Unable to create source file for `eval`")); + return; + } + code_file << code; + code_file.close(); + + /* Now to actually compile the file. We use dpp::utility::exec to + * invoke a compiler. This assumes you are using g++, and it is in your path. + */ + double compile_start = dpp::utility::time_f(); + dpp::utility::exec("g++", { + "-std=c++17", + "-shared", /* Build the output as a .so file */ + "-fPIC", + std::string("-o") + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so", + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp", + "-ldpp", + "-ldl" + }, [event, &bot, source_filename, compile_start](const std::string &output) { + + /* After g++ is ran we end up inside this lambda with the output as a string */ + double compile_time = dpp::utility::time_f() - compile_start; + + /* Delete our cpp file, we don't need it any more */ + std::string del_file = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp"; + unlink(del_file.c_str()); + + /* On successful compilation g++ outputs nothing, so any output here is error output */ + if (output.length()) { + bot.message_create(dpp::message(event.msg.channel_id, "Compile error: ```\n" + output + "\n```")); + } else { + + /* Now for the meat of the function. To actually load + * our shared object we use dlopen() to load it into the + * memory space of our bot. If dlopen() returns a nullptr, + * the shared object could not be loaded. The user probably + * did something odd with the symbols inside their eval. + */ + std::string dl = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so"; + auto shared_object_handle = dlopen(dl.c_str(), RTLD_NOW); + if (!shared_object_handle) { + const char *dlsym_error = dlerror(); + bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" + + std::string(dlsym_error ? dlsym_error : "Unknown error") +"\n```")); + return; + } + + /* This type represents the "void so_exec()" function inside + * the shared object library file. + */ + using function_pointer = void(*)(dpp::cluster&, dpp::message_create_t); + + /* Attempt to find the function called so_exec() inside the + * library we just loaded. If we can't find it, then the user + * did something really strange in their eval. Also note it's + * important we call dlerror() here to reset it before trying + * to use it a second time. It's weird-ass C code and is just + * like that. + */ + dlerror(); + function_pointer exec_run = (function_pointer)dlsym(shared_object_handle, "so_exec"); + const char *dlsym_error = dlerror(); + if (dlsym_error) { + bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" + std::string(dlsym_error) +"\n```")); + dlclose(shared_object_handle); + return; + } + + /* Now we have a function pointer to our actual exec code in + * 'exec_run', so lets call it, and pass it a reference to + * the cluster, and also a copy of the message_create_t. + */ + double run_start = dpp::utility::time_f(); + exec_run(bot, event); + double run_time = dpp::utility::time_f() - run_start; + + /* When we're done with a .so file we must always dlclose() it */ + dlclose(shared_object_handle); + + /* We are now done with the compiled code too */ + unlink(dl.c_str()); + + /* Output some statistics */ + bot.message_create(dpp::message(event.msg.channel_id, + "Execution completed. Compile time: " + std::to_string(compile_time) + + "s, execution time " + std::to_string(run_time) + "s")); + } + }); + } + }); + + bot.start(dpp::st_wait); + return 0; +} diff --git a/docpages/example_code/eval.h b/docpages/example_code/eval.h new file mode 100644 index 0000000000..0ce683e3f7 --- /dev/null +++ b/docpages/example_code/eval.h @@ -0,0 +1,14 @@ +#pragma once + +/* This is the snowflake ID of the bot's developer. + * The eval command will be restricted to this user. + */ +#define MY_DEVELOPER 189759562910400512ULL + +/* Any functions you want to be usable from within an eval, + * that are not part of D++ itself or the message event, you + * can put here as forward declarations. The test_function() + * serves as an example. + */ + +int test_function(); diff --git a/docpages/example_code/join_voice.cpp b/docpages/example_code/join_voice.cpp new file mode 100644 index 0000000000..266917afde --- /dev/null +++ b/docpages/example_code/join_voice.cpp @@ -0,0 +1,87 @@ +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + /* Setup the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "join") { + + /* Get the guild */ + dpp::guild* g = dpp::find_guild(event.command.guild_id); + + /* Get the voice channel that the bot is currently in from this server (will return nullptr if we're not in a voice channel!) */ + auto current_vc = event.from->get_voice(event.command.guild_id); + + bool join_vc = true; + + /* Are we in a voice channel? If so, let's see if we're in the right channel. */ + if (current_vc) { + /* Find the channel id that the user is currently in */ + auto users_vc = g->voice_members.find(event.command.get_issuing_user().id); + + if (users_vc != g->voice_members.end() && current_vc->channel_id == users_vc->second.channel_id) { + join_vc = false; + + /* We are on this voice channel, at this point we can send any audio instantly to vc: + + * current_vc->send_audio_raw(...) + */ + } else { + /* We are on a different voice channel. We should leave it, then join the new one + * by falling through to the join_vc branch below. + */ + event.from->disconnect_voice(event.command.guild_id); + + join_vc = true; + } + } + + /* If we need to join a vc at all, join it here if join_vc == true */ + if(join_vc) { + /* Attempt to connect to a voice channel, returns false if we fail to connect. */ + + /* The user issuing the command is not on any voice channel, we can't do anything */ + if (!g->connect_member_voice(event.command.get_issuing_user().id)) { + event.reply("You don't seem to be in a voice channel!"); + return; + } + + /* We are now connecting to a vc. Wait for on_voice_ready + * event, and then send the audio within that event: + * + * event.voice_client->send_audio_raw(...); + * + * NOTE: We can't instantly send audio, as we have to wait for + * the connection to the voice server to be established! + */ + + /* Tell the user we joined their channel. */ + event.reply("Joined your channel!"); + } else { + event.reply("Don't need to join your channel as i'm already there with you!"); + } + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new command. */ + bot.global_command_create(dpp::slashcommand("join", "Joins your voice channel.", bot.me.id)); + } + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/mp3.cpp b/docpages/example_code/mp3.cpp new file mode 100644 index 0000000000..a085df531e --- /dev/null +++ b/docpages/example_code/mp3.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* For an example we will hardcode a path to some awesome music here */ +#define MUSIC_FILE "/media/music/Rick Astley/Whenever You Need Somebody/Never Gonna Give You Up.mp3" + +int main(int argc, char const *argv[]) +{ + /* This will hold the decoded MP3. + * The D++ library expects PCM format, which are raw sound + * data, 2 channel stereo, 16 bit signed 48000Hz. + */ + std::vector pcmdata; + + mpg123_init(); + + int err = 0; + unsigned char* buffer; + size_t buffer_size, done; + int channels, encoding; + long rate; + + /* Note it is important to force the frequency to 48000 for Discord compatibility */ + mpg123_handle *mh = mpg123_new(NULL, &err); + mpg123_param(mh, MPG123_FORCE_RATE, 48000, 48000.0); + + /* Decode entire file into a vector. You could do this on the fly, but if you do that + * you may get timing issues if your CPU is busy at the time and you are streaming to + * a lot of channels/guilds. + */ + buffer_size = mpg123_outblock(mh); + buffer = new unsigned char[buffer_size]; + + /* Note: In a real world bot, this should have some error logging */ + mpg123_open(mh, MUSIC_FILE); + mpg123_getformat(mh, &rate, &channels, &encoding); + + unsigned int counter = 0; + for (int totalBytes = 0; mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK; ) { + for (size_t i = 0; i < buffer_size; i++) { + pcmdata.push_back(buffer[i]); + } + counter += buffer_size; + totalBytes += done; + } + delete buffer; + mpg123_close(mh); + mpg123_delete(mh); + + /* Setup the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot, &pcmdata](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "join") { + + /* Get the guild */ + dpp::guild* g = dpp::find_guild(event.command.guild_id); + + /* Attempt to connect to a voice channel, returns false if we fail to connect. */ + if (!g->connect_member_voice(event.command.get_issuing_user().id)) { + event.reply("You don't seem to be in a voice channel!"); + return; + } + + /* Tell the user we joined their channel. */ + event.reply("Joined your channel!"); + } else if (event.command.get_command_name() == "mp3") { + + /* Get the voice channel the bot is in, in this current guild. */ + dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + + /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ + if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { + event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!"); + return; + } + + /* Stream the already decoded MP3 file. This passes the PCM data to the library to be encoded to OPUS */ + v->voiceclient->send_audio_raw((uint16_t*)pcmdata.data(), pcmdata.size()); + + event.reply("Played the mp3 file."); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new command. */ + dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id); + + dpp::slashcommand mp3command("mp3", "Plays an mp3 file.", bot.me.id); + + bot.global_bulk_command_create({joincommand, mp3command}); + } + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + /* Clean up */ + mpg123_exit(); + + return 0; +} diff --git a/docpages/example_code/oggopus.cpp b/docpages/example_code/oggopus.cpp new file mode 100644 index 0000000000..6dd6ca4498 --- /dev/null +++ b/docpages/example_code/oggopus.cpp @@ -0,0 +1,117 @@ +#include +#include +#include + +#include +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + /* Load an ogg opus file into memory. + * The bot expects opus packets to be 2 channel stereo, 48000Hz. + * + * You may use ffmpeg to encode songs to ogg opus: + * ffmpeg -i /path/to/song -c:a libopus -ar 48000 -ac 2 -vn -b:a 96K /path/to/opus.ogg + */ + + /* Setup the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "join") { + + /* Get the guild */ + dpp::guild* g = dpp::find_guild(event.command.guild_id); + + /* Attempt to connect to a voice channel, returns false if we fail to connect. */ + if (!g->connect_member_voice(event.command.get_issuing_user().id)) { + event.reply("You don't seem to be in a voice channel!"); + return; + } + + /* Tell the user we joined their channel. */ + event.reply("Joined your channel!"); + } else if (event.command.get_command_name() == "play") { + + /* Get the voice channel the bot is in, in this current guild. */ + dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + + /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ + if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { + event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!"); + return; + } + + // load the audio file with oggz + OGGZ *track_og = oggz_open("/path/to/opus.ogg", OGGZ_READ); + + /* If there was an issue reading the file, tell the user and stop */ + if (!track_og) { + fprintf(stderr, "Error opening file\n"); + event.reply("There was an issue opening the file!"); + return; + } + + // set read callback, this callback will be called on packets with the serialno, + // -1 means every packet will be handled with this callback + oggz_set_read_callback( + track_og, -1, + [](OGGZ *oggz, oggz_packet *packet, long serialno, + void *user_data) { + dpp::voiceconn *voiceconn = (dpp::voiceconn *)user_data; + + // send the audio + voiceconn->voiceclient->send_audio_opus(packet->op.packet, + packet->op.bytes); + + // make sure to always return 0 here, read more on oggz documentation + return 0; + }, + // this will be the value of void *user_data + (void *)v + ); + + // read loop + while (v && v->voiceclient && !v->voiceclient->terminating) { + // you can tweak this to whatever. Here I use BUFSIZ, defined in + // stdio.h as 8192 + static const constexpr long CHUNK_READ = BUFSIZ * 2; + + const long read_bytes = oggz_read(track_og, CHUNK_READ); + + // break on eof + if (!read_bytes) + break; + } + + // don't forget to free the memory + oggz_close(track_og); + + event.reply("Finished playing the audio file!"); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new command. */ + dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id); + + dpp::slashcommand playcommand("play", "Plays an ogg file.", bot.me.id); + + bot.global_bulk_command_create({joincommand, playcommand}); + } + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/record_user.cpp b/docpages/example_code/record_user.cpp new file mode 100644 index 0000000000..7d17bb1f26 --- /dev/null +++ b/docpages/example_code/record_user.cpp @@ -0,0 +1,72 @@ +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + /* Example to record a user in a VC + * + * Recording is output as './me.pcm' and you can play it via the soundboard example + * or use ffmpeg 'ffplay -f s16le -ar 48000 -ac 2 -i ./me.pcm' + */ + + /* Replace with the user's id you wish to record */ + dpp::snowflake user_id = 407877550216314882; + + /* Setup the bot */ + dpp::cluster bot("token"); + + FILE *fd; + fd = fopen("./me.pcm", "wb"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot, &fd](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "record") { + + /* Get the guild */ + dpp::guild* g = dpp::find_guild(event.command.guild_id); + + /* Attempt to connect to a voice channel, returns false if we fail to connect. */ + if (!g->connect_member_voice(event.command.get_issuing_user().id)) { + event.reply("You don't seem to be in a voice channel!"); + return; + } + + /* Tell the user we joined their channel. */ + event.reply("Joined your channel, now recording!"); + } else if (event.command.get_command_name() == "stop") { + + event.from->disconnect_voice(event.command.guild_id); + fclose(fd); + + event.reply("Stopped recording."); + } + }); + + bot.on_voice_receive([&bot, &fd, &user_id](const dpp::voice_receive_t &event) { + if (event.user_id == user_id) { + fwrite((char *)event.audio, 1, event.audio_size, fd); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new command. */ + dpp::slashcommand recordcommand("record", "Joins your voice channel and records you.", bot.me.id); + + dpp::slashcommand stopcommand("stop", "Stops recording you.", bot.me.id); + + bot.global_bulk_command_create({recordcommand, stopcommand}); + } + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/soundboard.cpp b/docpages/example_code/soundboard.cpp new file mode 100644 index 0000000000..df1d326dab --- /dev/null +++ b/docpages/example_code/soundboard.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + /* Load a sound file called Robot.pcm into memory. + * The bot expects PCM format, which are raw sound data, + * 2 channel stereo, 16 bit signed 48000Hz. + * + * You can use audacity to export these from WAV or MP3 etc. + * + * If you wanted to send a more complicated format, you could + * use a separate library to decode that audio to PCM. For + * example purposes, a raw PCM will suffice. This PCM file can + * be found within the bot's github repo. + */ + uint8_t* robot = nullptr; + size_t robot_size = 0; + std::ifstream input ("../testdata/Robot.pcm", std::ios::in|std::ios::binary|std::ios::ate); + if (input.is_open()) { + robot_size = input.tellg(); + robot = new uint8_t[robot_size]; + input.seekg (0, std::ios::beg); + input.read ((char*)robot, robot_size); + input.close(); + } + + /* Setup the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot, robot, robot_size](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "join") { + + /* Get the guild */ + dpp::guild* g = dpp::find_guild(event.command.guild_id); + + /* Attempt to connect to a voice channel, returns false if we fail to connect. */ + if (!g->connect_member_voice(event.command.get_issuing_user().id)) { + event.reply("You don't seem to be in a voice channel!"); + return; + } + + /* Tell the user we joined their channel. */ + event.reply("Joined your channel!"); + } else if (event.command.get_command_name() == "robot") { + + /* Get the voice channel the bot is in, in this current guild. */ + dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + + /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ + if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { + event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!"); + return; + } + + /* Tell the bot to play the sound file 'Robot.pcm' in the current voice channel. */ + v->voiceclient->send_audio_raw((uint16_t*)robot, robot_size); + + event.reply("Played robot."); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new command. */ + dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id); + + dpp::slashcommand robotcommand("robot", "Plays a robot noise in your voice channel.", bot.me.id); + + bot.global_bulk_command_create({joincommand, robotcommand}); + } + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_programs/misc/eval.md b/docpages/example_programs/misc/eval.md index 6f65bb300f..4eb4972eb7 100644 --- a/docpages/example_programs/misc/eval.md +++ b/docpages/example_programs/misc/eval.md @@ -31,204 +31,13 @@ Docker is definitely recommended if you code on Windows/Mac OS, because docker d Remember that `eval.h` contains forward-declarations of any functions you want to expose to the eval command. It is included both by the bot itself, and by any shared object files compiled for evaluation. -~~~~~~~~~~~~~~~~{.cpp} -#pragma once - -/* This is the snowflake ID of the bot's developer. - * The eval command will be restricted to this user. - */ -#define MY_DEVELOPER 189759562910400512 - -/* Any functions you want to be usable from within an eval, - * that are not part of D++ itself or the message event, you - * can put here as forward declarations. The test_function() - * serves as an example. - */ - -int test_function(); -~~~~~~~~~~~~~~~~ +\include{cpp} eval.h #### eval.cpp This is the main body of the example program. -~~~~~~~~~~~~~~~~{.cpp} -/** - * D++ eval command example. - * This is dangerous and for educational use only, here be dragons! - */ - -#include -#include -#include -#include -/* We have to define this to make certain functions visible */ -#ifndef _GNU_SOURCE - #define _GNU_SOURCE -#endif -#include -#include -#include "eval.h" - -/* This is an example function you can expose to your eval command */ -int test_function() { - return 42; -} - -/* Important: This code is for UNIX-like systems only, e.g. - * Linux, BSD, OSX. It will NOT work on Windows! - * Note for OSX you'll probably have to change all references - * from .so to .dylib. - */ -int main() -{ - dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); - - bot.on_log(dpp::utility::cout_logger()); - - /* This won't work in a slash command very well yet, as there is not yet - * a multi-line slash command input type. - */ - bot.on_message_create([&bot](const auto & event) { - if (dpp::utility::utf8substr(event.msg.content, 0, 5) == "!eval") { - - /** - * THIS IS CRITICALLY IMPORTANT! - * Never EVER make an eval command that isn't restricted to a specific developer by user id. - * With access to this command the person who invokes it has at best full control over - * your bot's user account and at worst, full control over your entire network!!! - * Eval commands are Evil (pun intended) and could even be considered a security - * vulnerability. YOU HAVE BEEN WARNED! - */ - if (event.msg.author.id != MY_DEVELOPER) { - bot.message_create(dpp::message(event.msg.channel_id, "On the day i do this for you, Satan will be ice skating to work.")); - return; - } - - /* We start by creating a string that contains a cpp program for a simple library. - * The library will contain one exported function called so_exec() that is called - * containing the raw C++ code to eval. - */ - std::string code = "#include \n\ - #include \n\ - #include \n\ - #include \n\ - #include \n\ - #include \n\ - #include \n\ - #include \n\ - #include \"eval.h\"\n\ - extern \"C\" void so_exec(dpp::cluster& bot, dpp::message_create_t event) {\n\ - " + dpp::utility::utf8substr( - event.msg.content, - 6, - dpp::utility::utf8len(event.msg.content) - ) + ";\n\ - return;\n\ - }"; - - /* Next we output this string full of C++ to a cpp file on disk. - * This code assumes the current directory is writeable. The file will have a - * unique name made from the user's id and the message id. - */ - std::string source_filename = std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp"; - std::fstream code_file(source_filename, std::fstream::binary | std::fstream::out); - if (!code_file.is_open()) { - bot.message_create(dpp::message(event.msg.channel_id, "Unable to create source file for `eval`")); - return; - } - code_file << code; - code_file.close(); - - /* Now to actually compile the file. We use dpp::utility::exec to - * invoke a compiler. This assumes you are using g++, and it is in your path. - */ - double compile_start = dpp::utility::time_f(); - dpp::utility::exec("g++", { - "-std=c++17", - "-shared", /* Build the output as a .so file */ - "-fPIC", - std::string("-o") + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so", - std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp", - "-ldpp", - "-ldl" - }, [event, &bot, source_filename, compile_start](const std::string &output) { - - /* After g++ is ran we end up inside this lambda with the output as a string */ - double compile_time = dpp::utility::time_f() - compile_start; - - /* Delete our cpp file, we don't need it any more */ - std::string del_file = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp"; - unlink(del_file.c_str()); - - /* On successful compilation g++ outputs nothing, so any output here is error output */ - if (output.length()) { - bot.message_create(dpp::message(event.msg.channel_id, "Compile error: ```\n" + output + "\n```")); - } else { - - /* Now for the meat of the function. To actually load - * our shared object we use dlopen() to load it into the - * memory space of our bot. If dlopen() returns a nullptr, - * the shared object could not be loaded. The user probably - * did something odd with the symbols inside their eval. - */ - std::string dl = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so"; - auto shared_object_handle = dlopen(dl.c_str(), RTLD_NOW); - if (!shared_object_handle) { - const char *dlsym_error = dlerror(); - bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" + - std::string(dlsym_error ? dlsym_error : "Unknown error") +"\n```")); - return; - } - - /* This type represents the "void so_exec()" function inside - * the shared object library file. - */ - using function_pointer = void(*)(dpp::cluster&, dpp::message_create_t); - - /* Attempt to find the function called so_exec() inside the - * library we just loaded. If we can't find it, then the user - * did something really strange in their eval. Also note it's - * important we call dlerror() here to reset it before trying - * to use it a second time. It's weird-ass C code and is just - * like that. - */ - dlerror(); - function_pointer exec_run = (function_pointer)dlsym(shared_object_handle, "so_exec"); - const char *dlsym_error = dlerror(); - if (dlsym_error) { - bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" + std::string(dlsym_error) +"\n```")); - dlclose(shared_object_handle); - return; - } - - /* Now we have a function pointer to our actual exec code in - * 'exec_run', so lets call it, and pass it a reference to - * the cluster, and also a copy of the message_create_t. - */ - double run_start = dpp::utility::time_f(); - exec_run(bot, event); - double run_time = dpp::utility::time_f() - run_start; - - /* When we're done with a .so file we must always dlclose() it */ - dlclose(shared_object_handle); - - /* We are now done with the compiled code too */ - unlink(dl.c_str()); - - /* Output some statistics */ - bot.message_create(dpp::message(event.msg.channel_id, - "Execution completed. Compile time: " + std::to_string(compile_time) + - "s, execution time " + std::to_string(run_time) + "s")); - } - }); - } - }); - - bot.start(dpp::st_wait); - return 0; -} -~~~~~~~~~~~~~~~~ +\include{cpp} eval.cpp ### Compilation diff --git a/docpages/example_programs/music_and_audio/join_voice.md b/docpages/example_programs/music_and_audio/join_voice.md index 2e0920e64f..cfbf3d0f97 100644 --- a/docpages/example_programs/music_and_audio/join_voice.md +++ b/docpages/example_programs/music_and_audio/join_voice.md @@ -4,93 +4,4 @@ When a user issues a command you may want to join their voice channel, e.g. in a \note Please be aware this example sends no audio, but indicates clearly in the comments where and how you should do so. -~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include -#include -#include - -int main(int argc, char const *argv[]) -{ - /* Setup the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "join") { - - /* Get the guild */ - dpp::guild* g = dpp::find_guild(event.command.guild_id); - - /* Get the voice channel that the bot is currently in from this server (will return nullptr if we're not in a voice channel!) */ - auto current_vc = event.from->get_voice(event.command.guild_id); - - bool join_vc = true; - - /* Are we in a voice channel? If so, let's see if we're in the right channel. */ - if (current_vc) { - /* Find the channel id that the user is currently in */ - auto users_vc = g->voice_members.find(event.command.get_issuing_user().id); - - if (users_vc != g->voice_members.end() && current_vc->channel_id == users_vc->second.channel_id) { - join_vc = false; - - /* We are on this voice channel, at this point we can send any audio instantly to vc: - - * current_vc->send_audio_raw(...) - */ - } else { - /* We are on a different voice channel. We should leave it, then join the new one - * by falling through to the join_vc branch below. - */ - event.from->disconnect_voice(event.command.guild_id); - - join_vc = true; - } - } - - /* If we need to join a vc at all, join it here if join_vc == true */ - if(join_vc) { - /* Attempt to connect to a voice channel, returns false if we fail to connect. */ - - /* The user issuing the command is not on any voice channel, we can't do anything */ - if (!g->connect_member_voice(event.command.get_issuing_user().id)) { - event.reply("You don't seem to be in a voice channel!"); - return; - } - - /* We are now connecting to a vc. Wait for on_voice_ready - * event, and then send the audio within that event: - * - * event.voice_client->send_audio_raw(...); - * - * NOTE: We can't instantly send audio, as we have to wait for - * the connection to the voice server to be established! - */ - - /* Tell the user we joined their channel. */ - event.reply("Joined your channel!"); - } else { - event.reply("Don't need to join your channel as i'm already there with you!"); - } - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new command. */ - bot.global_command_create(dpp::slashcommand("join", "Joins your voice channel.", bot.me.id)); - } - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - return 0; -} - -~~~~~~~~~~~~~~~~~~~~~~~~~ \ No newline at end of file +\include{cpp} join_voice.cpp \ No newline at end of file diff --git a/docpages/example_programs/music_and_audio/mp3.md b/docpages/example_programs/music_and_audio/mp3.md index a6181a75d2..ee7db3212d 100644 --- a/docpages/example_programs/music_and_audio/mp3.md +++ b/docpages/example_programs/music_and_audio/mp3.md @@ -2,127 +2,7 @@ To stream MP3 files via D++ you need to link an additional dependency to your bot, namely `libmpg123`. It is relatively simple when linking this library to your bot to then decode audio to PCM and send it to the dpp::discord_voice_client::send_audio_raw function as shown below: - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -/* For an example we will hardcode a path to some awesome music here */ -#define MUSIC_FILE "/media/music/Rick Astley/Whenever You Need Somebody/Never Gonna Give You Up.mp3" - -int main(int argc, char const *argv[]) -{ - /* This will hold the decoded MP3. - * The D++ library expects PCM format, which are raw sound - * data, 2 channel stereo, 16 bit signed 48000Hz. - */ - std::vector pcmdata; - - mpg123_init(); - - int err = 0; - unsigned char* buffer; - size_t buffer_size, done; - int channels, encoding; - long rate; - - /* Note it is important to force the frequency to 48000 for Discord compatibility */ - mpg123_handle *mh = mpg123_new(NULL, &err); - mpg123_param(mh, MPG123_FORCE_RATE, 48000, 48000.0); - - /* Decode entire file into a vector. You could do this on the fly, but if you do that - * you may get timing issues if your CPU is busy at the time and you are streaming to - * a lot of channels/guilds. - */ - buffer_size = mpg123_outblock(mh); - buffer = new unsigned char[buffer_size]; - - /* Note: In a real world bot, this should have some error logging */ - mpg123_open(mh, MUSIC_FILE); - mpg123_getformat(mh, &rate, &channels, &encoding); - - unsigned int counter = 0; - for (int totalBytes = 0; mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK; ) { - for (auto i = 0; i < buffer_size; i++) { - pcmdata.push_back(buffer[i]); - } - counter += buffer_size; - totalBytes += done; - } - delete buffer; - mpg123_close(mh); - mpg123_delete(mh); - - /* Setup the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot, &pcmdata](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "join") { - - /* Get the guild */ - dpp::guild* g = dpp::find_guild(event.command.guild_id); - - /* Attempt to connect to a voice channel, returns false if we fail to connect. */ - if (!g->connect_member_voice(event.command.get_issuing_user().id)) { - event.reply("You don't seem to be in a voice channel!"); - return; - } - - /* Tell the user we joined their channel. */ - event.reply("Joined your channel!"); - } else if (event.command.get_command_name() == "mp3") { - - /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.channel.guild_id); - - /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ - if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { - event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!"); - return; - } - - /* Stream the already decoded MP3 file. This passes the PCM data to the library to be encoded to OPUS */ - v->voiceclient->send_audio_raw((uint16_t*)pcmdata.data(), pcmdata.size()); - - event.reply("Played the mp3 file."); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new command. */ - dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id); - - dpp::slashcommand mp3command("mp3", "Plays an mp3 file.", bot.me.id); - - bot.global_bulk_command_create({joincommand, mp3command}); - } - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - /* Clean up */ - mpg123_exit(); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} mp3.cpp To compile this program you must remember to specify `libmpg123` alongside `libdpp` in the build command, for example: diff --git a/docpages/example_programs/music_and_audio/oggopus.md b/docpages/example_programs/music_and_audio/oggopus.md index 991597b6c5..c41113dd54 100644 --- a/docpages/example_programs/music_and_audio/oggopus.md +++ b/docpages/example_programs/music_and_audio/oggopus.md @@ -194,125 +194,7 @@ You can use `liboggz` to stream an Ogg Opus file to discord voice channel. `liboggz` provides higher level abstraction and useful APIs. Some features `liboggz` provides include: seeking and timestamp interpretation. Read more on the [documentation](https://www.xiph.org/oggz/doc/). -~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include -#include -#include - -#include -#include -#include -#include - -int main(int argc, char const *argv[]) -{ - /* Load an ogg opus file into memory. - * The bot expects opus packets to be 2 channel stereo, 48000Hz. - * - * You may use ffmpeg to encode songs to ogg opus: - * ffmpeg -i /path/to/song -c:a libopus -ar 48000 -ac 2 -vn -b:a 96K /path/to/opus.ogg - */ - - /* Setup the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "join") { - - /* Get the guild */ - dpp::guild* g = dpp::find_guild(event.command.guild_id); - - /* Attempt to connect to a voice channel, returns false if we fail to connect. */ - if (!g->connect_member_voice(event.command.get_issuing_user().id)) { - event.reply("You don't seem to be in a voice channel!"); - return; - } - - /* Tell the user we joined their channel. */ - event.reply("Joined your channel!"); - } else if (event.command.get_command_name() == "play") { - - /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.channel.guild_id); - - /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ - if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { - event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!"); - return; - } - - // load the audio file with oggz - OGGZ *track_og = oggz_open("/path/to/opus.ogg", OGGZ_READ); - - /* If there was an issue reading the file, tell the user and stop */ - if (!track_og) { - fprintf(stderr, "Error opening file\n"); - event.reply("There was an issue opening the file!"); - return; - } - - // set read callback, this callback will be called on packets with the serialno, - // -1 means every packet will be handled with this callback - oggz_set_read_callback( - track_og, -1, - [](OGGZ *oggz, oggz_packet *packet, long serialno, - void *user_data) { - dpp::voiceconn *voiceconn = (dpp::voiceconn *)user_data; - - // send the audio - voiceconn->voiceclient->send_audio_opus(packet->op.packet, - packet->op.bytes); - - // make sure to always return 0 here, read more on oggz documentation - return 0; - }, - // this will be the value of void *user_data - (void *)v - ); - - // read loop - while (v && v->voiceclient && !v->voiceclient->terminating) { - // you can tweak this to whatever. Here I use BUFSIZ, defined in - // stdio.h as 8192 - static const constexpr long CHUNK_READ = BUFSIZ * 2; - - const long read_bytes = oggz_read(track_og, CHUNK_READ); - - // break on eof - if (!read_bytes) - break; - } - - // don't forget to free the memory - oggz_close(track_og); - - event.reply("Finished playing the audio file!"); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new command. */ - dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id); - - dpp::slashcommand playcommand("play", "Plays an ogg file.", bot.me.id); - - bot.global_bulk_command_create({joincommand, playcommand}); - } - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} oggopus.cpp You can compile this example using the following command: diff --git a/docpages/example_programs/music_and_audio/record_user.md b/docpages/example_programs/music_and_audio/record_user.md index f80f53f38e..022680d82c 100644 --- a/docpages/example_programs/music_and_audio/record_user.md +++ b/docpages/example_programs/music_and_audio/record_user.md @@ -4,78 +4,4 @@ DPP supports receiving audio. This examples show how to use it to record some us \note Voice receiving by bots is not officially supported by the Discord API. We cannot guarantee that this feature will work in the future. -~~~~~~~~~~{.cpp} -#include -#include -#include - -int main(int argc, char const *argv[]) -{ - /* Example to record a user in a VC - * - * Recording is output as './me.pcm' and you can play it via the soundboard example - * or use ffmpeg 'ffplay -f s16le -ar 48000 -ac 2 -i ./me.pcm' - */ - - /* Replace with the user's id you wish to record */ - dpp::snowflake user_id = 407877550216314882; - - /* Setup the bot */ - dpp::cluster bot("token"); - - FILE *fd; - fd = fopen("./me.pcm", "wb"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot, &fd](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "record") { - - /* Get the guild */ - dpp::guild* g = dpp::find_guild(event.command.guild_id); - - /* Attempt to connect to a voice channel, returns false if we fail to connect. */ - if (!g->connect_member_voice(event.command.get_issuing_user().id)) { - event.reply("You don't seem to be in a voice channel!"); - return; - } - - /* Tell the user we joined their channel. */ - event.reply("Joined your channel, now recording!"); - } else if (event.command.get_command_name() == "stop") { - - event.from->disconnect_voice(event.command.guild_id); - fclose(fd); - - event.reply("Stopped recording."); - } - }); - - bot.on_voice_receive([&bot, &fd, &user_id](const dpp::voice_receive_t &event) { - if (event.user_id == user_id) { - fwrite((char *)event.audio, 1, event.audio_size, fd); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new command. */ - dpp::slashcommand recordcommand("record", "Joins your voice channel and records you.", bot.me.id); - - dpp::slashcommand stopcommand("stop", "Stops recording you.", bot.me.id); - - bot.global_bulk_command_create({recordcommand, stopcommand}); - } - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ - +\include{cpp} record_user.cpp diff --git a/docpages/example_programs/music_and_audio/soundboard.md b/docpages/example_programs/music_and_audio/soundboard.md index 88d6940b6e..52818f6cab 100644 --- a/docpages/example_programs/music_and_audio/soundboard.md +++ b/docpages/example_programs/music_and_audio/soundboard.md @@ -2,90 +2,4 @@ This example script shows how to send a sound file to a voice channel. A few shortcuts are taken here, for more advanced techniques for connecting to a voice channel see the tutorial \ref joinvc -~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include -#include -#include - -int main(int argc, char const *argv[]) -{ - /* Load a sound file called Robot.pcm into memory. - * The bot expects PCM format, which are raw sound data, - * 2 channel stereo, 16 bit signed 48000Hz. - * - * You can use audacity to export these from WAV or MP3 etc. - * - * If you wanted to send a more complicated format, you could - * use a separate library to decode that audio to PCM. For - * example purposes, a raw PCM will suffice. This PCM file can - * be found within the bot's github repo. - */ - uint8_t* robot = nullptr; - size_t robot_size = 0; - std::ifstream input ("../testdata/Robot.pcm", std::ios::in|std::ios::binary|std::ios::ate); - if (input.is_open()) { - robot_size = input.tellg(); - robot = new uint8_t[robot_size]; - input.seekg (0, std::ios::beg); - input.read ((char*)robot, robot_size); - input.close(); - } - - /* Setup the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot, robot, robot_size](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "join") { - - /* Get the guild */ - dpp::guild* g = dpp::find_guild(event.command.guild_id); - - /* Attempt to connect to a voice channel, returns false if we fail to connect. */ - if (!g->connect_member_voice(event.command.get_issuing_user().id)) { - event.reply("You don't seem to be in a voice channel!"); - return; - } - - /* Tell the user we joined their channel. */ - event.reply("Joined your channel!"); - } else if (event.command.get_command_name() == "robot") { - - /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.channel.guild_id); - - /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ - if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { - event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!"); - return; - } - - /* Tell the bot to play the sound file 'Robot.pcm' in the current voice channel. */ - v->voiceclient->send_audio_raw((uint16_t*)robot, robot_size); - - event.reply("Played robot."); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new command. */ - dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id); - - dpp::slashcommand robotcommand("robot", "Plays a robot noise in your voice channel.", bot.me.id); - - bot.global_bulk_command_create({joincommand, robotcommand}); - } - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~ \ No newline at end of file +\include{cpp} soundboard.cpp \ No newline at end of file diff --git a/docpages/example_programs/the_basics/editing-channels-and-messages.md b/docpages/example_programs/the_basics/editing-channels-and-messages.md index 0cd2099953..e36258d358 100644 --- a/docpages/example_programs/the_basics/editing-channels-and-messages.md +++ b/docpages/example_programs/the_basics/editing-channels-and-messages.md @@ -4,81 +4,7 @@ Sometimes we need to update an object, such as message or channel. At first, it \note This example uses callback functions. To see more information about them, visit \ref callback-functions. -~~~~~~~~~~{.cpp} -#include - -int main() { - dpp::cluster bot("Token", dpp::i_default_intents | dpp::i_message_content); - /* the second argument is a bitmask of intents - i_message_content is needed to get messages */ - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - if (event.command.get_command_name() == "msg-send") { - event.reply("That's a message"); - } else if (event.command.get_command_name() == "msg-edit") { - const auto content = std::get(event.get_parameter("content")); - - /* get message to edit it after */ - const dpp::snowflake msg_id = std::get(event.get_parameter("msg-id")); - /* here string will automatically be converted to snowflake */ - - bot.message_get(msg_id, event.command.channel_id, [&bot, content, event](const dpp::confirmation_callback_t& callback) { - if (callback.is_error()) { - event.reply("error"); - return; - } - auto message = callback.get(); - - /* change the message content and edit the message itself */ - message.set_content(content); - bot.message_edit(message); - event.reply("Message content is now `" + content + "`."); - }); - } else if (event.command.get_command_name() == "channel-edit") { - const auto name = std::get(event.get_parameter("name")); - - /* get the channel to edit it after */ - const auto channel_id = std::get(event.get_parameter("channel")); - bot.channel_get(channel_id, [&bot, name, event](const dpp::confirmation_callback_t& callback) { - if (callback.is_error()) { - event.reply("error"); - return; - } - auto channel = callback.get(); - - /* change the channel name and edit the channel itself */ - channel.set_name(name); - bot.channel_edit(channel); - event.reply("Channel name is now `" + name + "`."); - }); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - - if (dpp::run_once ()) { - dpp::slashcommand msg_edit("msg-edit", "Edit a message sent by the bot", bot.me.id); - - msg_edit.add_option(dpp::command_option(dpp::co_string, "msg-id", "ID of the message to edit", true)); /* true for required option */ - msg_edit.add_option(dpp::command_option(dpp::co_string, "content", "New content for the message", true)); /* same here */ - - dpp::slashcommand channel_edit("channel-edit", "Edit the name of channel specified", bot.me.id); - - channel_edit.add_option(dpp::command_option(dpp::co_channel, "channel", "Channel to edit", true)); - channel_edit.add_option(dpp::command_option(dpp::co_string, "name", "New name for the channel", true)); - - dpp::slashcommand msg_send("msg-send", "Send my message", bot.me.id); - - bot.global_bulk_command_create({ msg_edit, channel_edit, msg_send }); - } - }); - - bot.start(dpp::st_wait); - return 0; -} -~~~~~~~~~~ +\example{cpp} editing_messages.cpp Before editing: diff --git a/docpages/example_programs/the_basics/using_callback_functions.md b/docpages/example_programs/the_basics/using_callback_functions.md index 9da5142eb4..587be849f9 100644 --- a/docpages/example_programs/the_basics/using_callback_functions.md +++ b/docpages/example_programs/the_basics/using_callback_functions.md @@ -2,96 +2,7 @@ When you create or get an object from Discord, you send the request to its API and in return you get either an error or the object you requested/created. You can pass a function to API calls as the callback function. This means that when the request completes, and you get a response from the API, your callback function executes. You must be careful with lambda captures! Good practice would be not capturing variables by reference unless you have to, since when the request completes and the function executes, the variables can already be destructed. Advanced reference can be found [here](https://dpp.dev/lambdas-and-locals.html). Now, let's see callback functions in action: -~~~~~~~~~~~~~~{.cpp} -#include - -int main() { - dpp::cluster bot("Token Was Here", dpp::i_default_intents | dpp::i_message_content); - /* the second argument is a bitmask of intents - i_message_content is needed to get messages */ - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) -> void { - if (event.command.get_command_name() == "msgs-get") { - int64_t limit = std::get(event.get_parameter("quantity")); - - /* get messages using ID of the channel the command was issued in */ - bot.messages_get(event.command.channel_id, 0, 0, 0, limit, [event](const dpp::confirmation_callback_t& callback) -> void { - if (callback.is_error()) { /* catching an error to log it */ - std::cout << callback.get_error().message << std::endl; - return; - } - - auto messages = callback.get(); - /* std::get(callback.value) would give the same result */ - - std::string contents; - for (const auto& x : messages) { /* here we iterate through the dpp::message_map we got from callback... */ - contents += x.second.content + '\n'; /* ...where x.first is ID of the current message and x.second is the message itself. */ - } - - event.reply(contents); /* we will see all those messages we got, united as one! */ - }); - } else if (event.command.get_command_name() == "channel-create") { - /* create a text channel */ - dpp::channel channel = dpp::channel() - .set_name("test") - .set_guild_id(event.command.guild_id); - - bot.channel_create(channel, [&bot, event](const dpp::confirmation_callback_t& callback) -> void { - if (callback.is_error()) { /* catching an error to log it */ - bot.log(dpp::loglevel::ll_error, callback.get_error().message); - return; - } - - auto channel = callback.get(); - /* std::get(callback.value) would give the same result */ - - /* reply with the created channel information */ - dpp::message message = dpp::message("The channel's name is `" + channel.name + "`, ID is `" + std::to_string(channel.id) + " and type is `" + std::to_string(channel.get_type()) + "`."); - /* note that channel types are represented as numbers */ - event.reply(message); - }); - } else if (event.command.get_command_name() == "msg-error") { - bot.message_get(0, 0, [event](const dpp::confirmation_callback_t& callback) -> void { - /* the error will occur since there is no message with ID '0' that is in a channel with ID '0' (I'm not explaining why) */ - if (callback.is_error()) { - event.reply(callback.get_error().message); - return; - } - - /* we won't be able to get here because of the return; statement */ - auto message = callback.get(); - event.reply(message); - }); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once ()) { - dpp::slashcommand msgs_get("msgs-get", "Get messages", bot.me.id); - - constexpr int64_t min_val{1}; - constexpr int64_t max_val{100}; - - msgs_get.add_option( - dpp::command_option(dpp::co_integer, "quantity", "Quantity of messages to get. Max - 100.") - .set_min_value(min_val) - .set_max_value(max_val) - ); - - dpp::slashcommand channel_create("channel-create", "Create a channel", bot.me.id); - dpp::slashcommand msg_error("msg-error", "Get an error instead of message :)", bot.me.id); - - bot.global_bulk_command_create({ msgs_get, channel_create, msg_error }); - } - }); - - bot.start(dpp::st_wait); - return 0; -} -~~~~~~~~~~~~~~ +\include{cpp} callbacks.cpp This is the result: diff --git a/docpages/example_programs/using_coroutines/awaiting_events.md b/docpages/example_programs/using_coroutines/awaiting_events.md index 869c72e975..423d840f84 100644 --- a/docpages/example_programs/using_coroutines/awaiting_events.md +++ b/docpages/example_programs/using_coroutines/awaiting_events.md @@ -6,47 +6,7 @@ D++ makes it possible to await events: simple use `co_await` on any of the event \note When the event router resumes your coroutine, it will give you __a reference to the event object__. This will likely mean it will be destroyed after your next co_await, make sure to save it in a local variable if you need it for longer. -~~~~~~~~~~cpp -#include - -int main() { - dpp::cluster bot{"token"}; - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { - if (event.command.get_command_name() == "test") { - // Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it - dpp::message m{"Test"}; - std::string id{event.command.id.str()}; - m.add_component( - dpp::component{}.add_component(dpp::component{}.set_type(dpp::cot_button).set_label("Click me!").set_id(id)) - ); - co_await event.co_reply(m); - - dpp::button_click_t click_event = co_await event.from->creator->on_button_click.when( - // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference here or the compiler will destroy it twice. This is fixed in g++13 - [&id] (dpp::button_click_t const &b) { - return b.custom_id == id; - } - ); - // Acknowledge the click and edit the original response, removing the button - click_event.reply(); - event.edit_original_response(dpp::message{"You clicked the button!"}); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id}; - - bot.global_command_create(command); - } - }); - - bot.start(dpp::st_wait); -} -~~~~~~~~~~ +\example{cpp} coro_awaiting_events.cpp Note that there is a problem with that! If the user never clicks your button, or if the message gets deleted, your coroutine will be stuck waiting... And waiting... Forever until your bot shuts down, occupying a space in memory. This is where the \ref expiring-buttons "next example" comes into play as a solution, with a button that expires with time. diff --git a/docpages/example_programs/using_coroutines/coro_introduction.md b/docpages/example_programs/using_coroutines/coro_introduction.md index 217f7be4bf..55c5e123c2 100644 --- a/docpages/example_programs/using_coroutines/coro_introduction.md +++ b/docpages/example_programs/using_coroutines/coro_introduction.md @@ -5,44 +5,7 @@ Introduced in C++20, coroutines are the solution to the impracticality of callba Let's revisit \ref attach-file "attaching a downloaded file", but this time with a coroutine: -~~~~~~~~~~~~~~~cpp -#include - -int main() { - dpp::cluster bot{"token"}; - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - /* Make note of passing the event by value, this is important (explained below) */ - bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { - if (event.command.get_command_name() == "file") { - /* Request the image from the URL specified and co_await the response */ - dpp::http_request_completion_t result = co_await event.from->creator->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get); - - /* Create a message and attach the image on success */ - dpp::message msg(event.command.channel_id, "This is my new attachment:"); - if (result.status == 200) { - msg.add_file("logo.png", result.body); - } - - /* Send the message, with our attachment. */ - event.reply(msg); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand{"file", "Send a message with an image attached from the internet!", bot.me.id}); - } - }); - - bot.start(dpp::st_wait); - return 0; -} -~~~~~~~~~~~~~~~ - +\include{cpp} coro_intro.cpp Coroutines can make commands simpler by eliminating callbacks, which can be very handy in the case of complex commands that rely on a lot of different data or steps. diff --git a/docpages/example_programs/using_coroutines/coro_simple_commands.md b/docpages/example_programs/using_coroutines/coro_simple_commands.md index cb47dcca17..0ec97b8437 100644 --- a/docpages/example_programs/using_coroutines/coro_simple_commands.md +++ b/docpages/example_programs/using_coroutines/coro_simple_commands.md @@ -8,71 +8,7 @@ With coroutines, it becomes a lot easier to do several asynchronous requests for one task. As an example an "addemoji" command taking a file and a name as a parameter. This means downloading the emoji, submitting it to Discord, and finally replying, with some error handling along the way. Normally we would have to use callbacks and some sort of object keeping track of our state, but with coroutines, the function can simply pause and be resumed when we receive the response to our request : -~~~~~~~~~~cpp -#include - -int main() { - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { - if (event.command.get_command_name() == "addemoji") { - dpp::cluster *cluster = event.from->creator; - // Retrieve parameter values - dpp::snowflake file_id = std::get(event.get_parameter("file")); - std::string emoji_name = std::get(event.get_parameter("name")); - - // Get the attachment from the resolved list - const dpp::attachment &attachment = event.command.get_resolved_attachment(file_id); - - // For simplicity for this example we only support PNG - if (attachment.content_type != "image/png") { - // While we could use event.co_reply, we can just use event.reply, as we will exit the command anyway and don't need to wait on the result - event.reply("Error: type " + attachment.content_type + " not supported"); - co_return; - } - // Send a " is thinking..." message, to wait on later so we can edit - dpp::async thinking = event.co_thinking(false); - - // Download and co_await the result - dpp::http_request_completion_t response = co_await cluster->co_request(attachment.url, dpp::m_get); - - if (response.status != 200) { // Page didn't send the image - co_await thinking; // Wait for the thinking response to arrive so we can edit - event.edit_response("Error: could not download the attachment"); - } else { - // Load the image data in a dpp::emoji - dpp::emoji emoji(emoji_name); - emoji.load_image(response.body, dpp::image_type::i_png); - - // Create the emoji and co_await the response - dpp::confirmation_callback_t confirmation = co_await cluster->co_guild_emoji_create(event.command.guild_id, emoji); - - co_await thinking; // Wait for the thinking response to arrive so we can edit - if (confirmation.is_error()) { - event.edit_response("Error: could not add emoji: " + confirmation.get_error().message); - } else { // Success - event.edit_response("Successfully added " + confirmation.get().get_mention()); // Show the new emoji - } - } - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - dpp::slashcommand command("addemoji", "Add an emoji", bot.me.id); - // Add file and name as required parameters - command.add_option(dpp::command_option(dpp::co_attachment, "file", "Select an image", true)); - command.add_option(dpp::command_option(dpp::co_string, "name", "Name of the emoji to add", true)); - - bot.global_command_create(command); - } - }); - - bot.start(dpp::st_wait); -} -~~~~~~~~~~ +\include{cpp} coro_simple_commands1.cpp ### I heard you liked tasks @@ -82,87 +18,4 @@ Earlier we mentioned two other types of coroutines provided by dpp: dpp::corouti Here is an example of a command making use of dpp::task to retrieve the avatar of a specified user, or if missing, the sender: -~~~~~~~~~~cpp -#include - -int main() { - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { - if (event.command.get_command_name() == "avatar") { - // Make a nested coroutine to fetch the guild member requested, that returns it as an optional - constexpr auto resolve_member = [](const dpp::slashcommand_t &event) -> dpp::task> { - const dpp::command_value &user_param = event.get_parameter("user"); - dpp::snowflake user_id; - if (std::holds_alternative(user_param)) { - user_id = event.command.usr.id; // Parameter is empty so user is sender - } - else if (std::holds_alternative(user_param)) { - user_id = std::get(user_param); // Parameter has a user - } - - // If we have the guild member in the command's resolved data, return it - const auto &member_map = event.command.resolved.members; - if (auto member = member_map.find(user_id); member != member_map.end()) - co_return member->second; - // Try looking in guild cache - dpp::guild *guild = dpp::find_guild(event.command.guild_id); - if (guild) { - // Look in guild's member cache - if (auto member = guild->members.find(user_id); member != guild->members.end()) { - co_return member->second; - } - } - // Finally if everything else failed, request API - dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_guild_get_member(event.command.guild_id, user_id); - if (confirmation.is_error()) { - co_return std::nullopt; // Member not found, return empty - } else { - co_return confirmation.get(); - } - }; - - // Send a " is thinking..." message, to wait on later so we can edit - dpp::async thinking = event.co_thinking(false); - // Call our coroutine defined above to retrieve the member requested - std::optional member = co_await resolve_member(event); - if (!member.has_value()) { - // Wait for the thinking response to arrive to make sure we can edit - co_await thinking; - event.edit_original_response(dpp::message{"User not found in this server!"}); - co_return; - } - - std::string avatar_url = member->get_avatar_url(512); - if (avatar_url.empty()) { // Member does not have a custom avatar for this server, get their user avatar - dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_user_get_cached(member->user_id); - if (confirmation.is_error()) { - // Wait for the thinking response to arrive to make sure we can edit - co_await thinking; - event.edit_original_response(dpp::message{"User not found!"}); - co_return; - } - avatar_url = confirmation.get().get_avatar_url(512); - } - - // Wait for the thinking response to arrive to make sure we can edit - co_await thinking; - event.edit_original_response(dpp::message{avatar_url}); - } - }); - - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - dpp::slashcommand command("avatar", "Get your or another user's avatar image", bot.me.id); - command.add_option(dpp::command_option(dpp::co_user, "user", "User to fetch the avatar from")); - - bot.global_command_create(command); - } - }); - - bot.start(dpp::st_wait); -} -~~~~~~~~~~ +\include{cpp} coro_simple_commands2.cpp diff --git a/docpages/example_programs/using_coroutines/expiring_buttons.md b/docpages/example_programs/using_coroutines/expiring_buttons.md index 1444d6b3c8..f07234714e 100644 --- a/docpages/example_programs/using_coroutines/expiring_buttons.md +++ b/docpages/example_programs/using_coroutines/expiring_buttons.md @@ -4,51 +4,7 @@ In the last example we've explored how to \ref awaiting-events "await events" using coroutines, we ran into the problem of the coroutine waiting forever if the button was never clicked. Wouldn't it be nice if we could add an "or" to our algorithm, for example wait for the button to be clicked *or* for a timer to expire? I'm glad you asked! D++ offers \ref dpp::when_any "when_any" which allows exactly that. It is a templated class that can take any number of awaitable objects and can be `co_await`-ed itself, will resume when the __first__ awaitable completes and return a \ref dpp::when_any::result "result" object that allows to retrieve which awaitable completed as well as its result, in a similar way as std::variant. -~~~~~~~~~~cpp -#include - -int main() { - dpp::cluster bot{"token"}; - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job { - if (event.command.get_command_name() == "test") { - // Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it - dpp::message m{"Test"}; - std::string id{event.command.id.str()}; - m.add_component( - dpp::component{}.add_component(dpp::component{}.set_type(dpp::cot_button).set_label("Click me!").set_id(id)) - ); - co_await event.co_reply(m); - - auto result = co_await dpp::when_any{ // Whichever completes first... - event.from->creator->on_button_click.when([&id](const dpp::button_click_t &b) { return b.custom_id == id; }), // Button clicked - event.from->creator->co_sleep(5) // Or sleep 5 seconds - }; - // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference above or the compiler will destroy it twice. This is fixed in g++13 - if (result.index() == 0) { // Awaitable #0 completed first, that is the button click event - // Acknowledge the click and edit the original response, removing the button - const dpp::button_click_t &click_event = result.get<0>(); - click_event.reply(); - event.edit_original_response(dpp::message{"You clicked the button with the id " + click_event.custom_id}); - } else { // Here index() is 1, the timer expired - event.edit_original_response(dpp::message{"I haven't got all day!"}); - } - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id}; - - bot.global_command_create(command); - } - }); - - bot.start(dpp::st_wait); -} -~~~~~~~~~~ +\include{cpp} coro_expiring_buttons.cpp Any awaitable can be used with when_any, even dpp::task, dpp::coroutine, dpp::async. When the when_any object is destroyed, any of its awaitables with a cancel() method (for example \ref dpp::task::cancel "dpp::task") will have it called. With this you can easily make commands that ask for input in several steps, or maybe a timed text game, the possibilities are endless! Note that if the first awaitable completes with an exception, result.get will throw it. From af6ce1522cd8b0acd4f2f2f61bb0aedb0a59c978 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Sep 2023 23:52:11 +0000 Subject: [PATCH 06/13] misc examples --- docpages/example_code/cache_messages.cpp | 60 +++++++++++++++++ docpages/example_code/collect_reactions.cpp | 46 +++++++++++++ docpages/example_code/http_request.cpp | 28 ++++++++ docpages/example_code/setting_status1.cpp | 20 ++++++ docpages/example_code/setting_status2.cpp | 26 ++++++++ .../example_programs/misc/cache_messages.md | 64 +------------------ .../misc/collect_reactions.md | 49 +------------- .../example_programs/misc/http_request.md | 30 +-------- .../example_programs/misc/setting_status.md | 52 +-------------- 9 files changed, 185 insertions(+), 190 deletions(-) create mode 100644 docpages/example_code/cache_messages.cpp create mode 100644 docpages/example_code/collect_reactions.cpp create mode 100644 docpages/example_code/http_request.cpp create mode 100644 docpages/example_code/setting_status1.cpp create mode 100644 docpages/example_code/setting_status2.cpp diff --git a/docpages/example_code/cache_messages.cpp b/docpages/example_code/cache_messages.cpp new file mode 100644 index 0000000000..8267971bb1 --- /dev/null +++ b/docpages/example_code/cache_messages.cpp @@ -0,0 +1,60 @@ +#include +#include + +int main() { + /* Create bot */ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); /* Because we're handling messages, we need to use the "i_message_content" intent! */ + + /* Create a cache to contain types of dpp::message */ + dpp::cache message_cache; + + bot.on_log(dpp::utility::cout_logger()); + + /* Message handler */ + bot.on_message_create([&](const dpp::message_create_t &event) { + /* Make a permanent pointer using new, for each message to be cached */ + dpp::message* m = new dpp::message(); + + /* Store the message into the pointer by copying it */ + *m = event.msg; + + /* Store the new pointer to the cache using the store() method */ + message_cache.store(m); + }); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot, &message_cache](const dpp::slashcommand_t& event) { + /* Check which command they ran */ + if (event.command.get_command_name() == "get") { + + dpp::message* find_msg = message_cache.find(std::get(event.get_parameter("message_id"))); + + /* If find_msg is null, tell the user and return. */ + if (!find_msg) { + event.reply("There is no message cached with this ID"); + return; + } + + event.reply("This message had the following content: " + find_msg->content); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create a new command. */ + dpp::slashcommand newcommand("get", "Get the contents of a message that was cached via an id", bot.me.id); + + /* Add a parameter option. */ + newcommand.add_option(dpp::command_option(dpp::co_string, "message_id", "The ID of the message you want to find", true)); + + /* Register the command */ + bot.global_command_create(newcommand); + } + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/collect_reactions.cpp b/docpages/example_code/collect_reactions.cpp new file mode 100644 index 0000000000..4d796643cf --- /dev/null +++ b/docpages/example_code/collect_reactions.cpp @@ -0,0 +1,46 @@ +#include + +/* To create a collector we must derive from dpp::collector. As dpp::collector is a complicated template, + * various pre-made forms exist such as this one, reaction_collector. + */ +class react_collector : public dpp::reaction_collector { +public: + /* Collector will run for 20 seconds */ + react_collector(dpp::cluster* cl, dpp::snowflake id) : dpp::reaction_collector(cl, 20, id) { } + + /* Override the "completed" event and then output the number of collected reactions as a message. */ + virtual void completed(const std::vector& list) override { + if (list.size()) { + owner->message_create(dpp::message(list[0].react_channel->id, "I collected " + std::to_string(list.size()) + " reactions!")); + } else { + owner->message_create(dpp::message("... I got nothin'.")); + } + } +}; + + +int main() { + /* Create bot */ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); + + /* Pointer to reaction collector */ + react_collector* r = nullptr; + + bot.on_log(dpp::utility::cout_logger()); + + /* Message handler */ + bot.on_message_create([&r, &bot](const dpp::message_create_t& event) { + + /* If someone sends a message that has the text 'collect reactions!' start a reaction collector */ + if (event.msg.content == "collect reactions!" && r == nullptr) { + /* Create a new reaction collector to collect reactions */ + r = new react_collector(&bot, event.msg.id); + } + + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/http_request.cpp b/docpages/example_code/http_request.cpp new file mode 100644 index 0000000000..67252bdc88 --- /dev/null +++ b/docpages/example_code/http_request.cpp @@ -0,0 +1,28 @@ +#include +#include + +int main() { + dpp::cluster bot("TOKEN GOES HERE"); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_ready([&bot](const dpp::ready_t& event) { + // Arbitrary post data as a string + std::string mypostdata = "{\"value\": 42}"; + // Make a HTTP POST request. HTTP and HTTPS are supported here. + bot.request( + "http://www.somebotlist.com/api/servers", dpp::m_post, [](const dpp::http_request_completion_t & cc) { + // This callback is called when the HTTP request completes. See documentation of + // dpp::http_request_completion_t for information on the fields in the parameter. + std::cout << "I got reply: " << cc.body << " with HTTP status code: " << cc.status << "\n"; + }, + mypostdata, + "application/json", + { + {"Authorization", "Bearer tokengoeshere"} + } + ); + }); + + bot.start(dpp::st_wait); +} diff --git a/docpages/example_code/setting_status1.cpp b/docpages/example_code/setting_status1.cpp new file mode 100644 index 0000000000..0c1cc0a451 --- /dev/null +++ b/docpages/example_code/setting_status1.cpp @@ -0,0 +1,20 @@ +#include + +int main() +{ + /* Create the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_ready([&bot](const dpp::ready_t& event) { + /* We don't need the run_once here as we're not registering commands! */ + + /* Set the bot presence as online and "Playing..."! */ + bot.set_presence(dpp::presence(dpp::ps_online, dpp::at_game, "games!")); + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/setting_status2.cpp b/docpages/example_code/setting_status2.cpp new file mode 100644 index 0000000000..947ac4c72c --- /dev/null +++ b/docpages/example_code/setting_status2.cpp @@ -0,0 +1,26 @@ +#include + +int main() +{ + /* Create the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_ready([&bot](const dpp::ready_t& event) { + /* We put our status updating inside "run_once" so that multiple shards don't try do this as "set_presence" updates all the shards. */ + if (dpp::run_once()) { + /* We update the presence now as the timer will do the first execution after the x amount of seconds we specify */ + bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_game, "with " + std::to_string(dpp::get_guild_cache()->count()) + " guilds!")); + + /* Create a timer that runs every 120 seconds, that sets the status */ + bot.start_timer([&bot](const dpp::timer& timer) { + bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_game, "with " + std::to_string(dpp::get_guild_cache()->count()) + " guilds!")); + }, 120); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_programs/misc/cache_messages.md b/docpages/example_programs/misc/cache_messages.md index 865cedd8f5..25c9782724 100644 --- a/docpages/example_programs/misc/cache_messages.md +++ b/docpages/example_programs/misc/cache_messages.md @@ -8,66 +8,4 @@ This can be adjusted to cache any type derived from dpp::managed including types you should use the dpp::cache::remove() method periodically to remove stale items. This is left out of this example as a learning exercise to the reader. For further reading please see the documentation of dpp::cache -~~~~~~~~~~{.cpp} -#include -#include - -int main() { - /* Create bot */ - dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); /* Because we're handling messages, we need to use the "i_message_content" intent! */ - - /* Create a cache to contain types of dpp::message */ - dpp::cache message_cache; - - bot.on_log(dpp::utility::cout_logger()); - - /* Message handler */ - bot.on_message_create([&](const dpp::message_create_t &event) { - /* Make a permanent pointer using new, for each message to be cached */ - dpp::message* m = new dpp::message(); - - /* Store the message into the pointer by copying it */ - *m = event.msg; - - /* Store the new pointer to the cache using the store() method */ - message_cache.store(m); - }); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - /* Check which command they ran */ - if (event.command.get_command_name() == "get") { - - dpp::message* find_msg = message_cache.find(std::get(event.get_parameter("message_id"))); - - /* If find_msg is null, tell the user and return. */ - if (!find_msg) { - event.reply("There is no message cached with this ID"); - return; - } - - event.reply("This message had the following content: " + find_msg->content); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create a new command. */ - dpp::slashcommand newcommand("get", "Get the contents of a message that was cached via an id", bot.me.id); - - /* Add a parameter option. */ - newcommand.add_option(dpp::command_option(dpp::co_string, "message_id", "The ID of the message you want to find", true)); - - /* Register the command */ - bot.global_command_create(newcommand); - } - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ - +\include{cpp} cache_messages.cpp diff --git a/docpages/example_programs/misc/collect_reactions.md b/docpages/example_programs/misc/collect_reactions.md index 6a290e43d5..cbd671e5fa 100644 --- a/docpages/example_programs/misc/collect_reactions.md +++ b/docpages/example_programs/misc/collect_reactions.md @@ -4,52 +4,5 @@ D++ comes with many useful helper classes, but amongst these is something called In the example below we will use it to collect all reactions on a message. -~~~~~~~~~~{.cpp} -#include - -/* To create a collector we must derive from dpp::collector. As dpp::collector is a complicated template, - * various pre-made forms exist such as this one, reaction_collector. - */ -class react_collector : public dpp::reaction_collector { -public: - /* Collector will run for 20 seconds */ - react_collector(dpp::cluster* cl, snowflake id) : dpp::reaction_collector(cl, 20, id) { } - - /* Override the "completed" event and then output the number of collected reactions as a message. */ - virtual void completed(const std::vector& list) override { - if (list.size()) { - owner->message_create(dpp::message(list[0].react_channel->id, "I collected " + std::to_string(list.size()) + " reactions!")); - } else { - owner->message_create(dpp::message("... I got nothin'.")); - } - } -}; - - -int main() { - /* Create bot */ - dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); - - /* Pointer to reaction collector */ - react_collector* r = nullptr; - - bot.on_log(dpp::utility::cout_logger()); - - /* Message handler */ - bot.on_message_create([&](const dpp::message_create_t& event) { - - /* If someone sends a message that has the text 'collect reactions!' start a reaction collector */ - if (event.msg.content == "collect reactions!" && r == nullptr) { - /* Create a new reaction collector to collect reactions */ - r = new react_collector(&bot, event.msg.id); - } - - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} collect_reactions.cpp diff --git a/docpages/example_programs/misc/http_request.md b/docpages/example_programs/misc/http_request.md index ff01ba5e0c..523a781fda 100644 --- a/docpages/example_programs/misc/http_request.md +++ b/docpages/example_programs/misc/http_request.md @@ -2,32 +2,4 @@ If you wish to make arbitrary HTTP(S) requests to websites and APIs, e.g. to update statistics on bot lists, you can use code similar to the code below. You may pass any arbitrary POST data: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() { - dpp::cluster bot("TOKEN GOES HERE"); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_ready([&bot](const dpp::ready_t& event) { - // Arbitrary post data as a string - std::string mypostdata = "{\"value\": 42}"; - // Make a HTTP POST request. HTTP and HTTPS are supported here. - bot.request( - "http://www.somebotlist.com/api/servers", dpp::m_post, [](const dpp::http_request_completion_t & cc) { - // This callback is called when the HTTP request completes. See documentation of - // dpp::http_request_completion_t for information on the fields in the parameter. - std::cout << "I got reply: " << cc.body << " with HTTP status code: " << cc.status << "\n"; - }, - mypostdata, - "application/json", - { - {"Authorization", "Bearer tokengoeshere"} - } - ); - }); - - bot.start(dpp::st_wait); -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} http_request.cpp diff --git a/docpages/example_programs/misc/setting_status.md b/docpages/example_programs/misc/setting_status.md index b4f6dc0253..a487ec8a54 100644 --- a/docpages/example_programs/misc/setting_status.md +++ b/docpages/example_programs/misc/setting_status.md @@ -4,28 +4,7 @@ A bot status is pretty cool, and it'd be cooler if you knew how to do it! This t First, we'll cover setting the bot status to `Playing games!`. -~~~~~~~~~~{.cpp} -#include - -int main() -{ - /* Create the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_ready([&bot](const dpp::ready_t& event) { - /* We don't need the run_once here as we're not registering commands! */ - - /* Set the bot presence as online and "Playing..."! */ - bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_game, "games!")); - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} setting_status1.cpp If all went well, your bot should now be online and say this on members list! @@ -33,34 +12,7 @@ If all went well, your bot should now be online and say this on members list! Now, let's cover setting the bot status to say `Playing with x guilds!` every two minutes. -~~~~~~~~~~{.cpp} -#include - -int main() -{ - /* Create the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_ready([&bot](const dpp::ready_t& event) { - /* We put our status updating inside "run_once" so that multiple shards don't try do this as "set_presence" updates all the shards. */ - if (dpp::run_once()) { - /* We update the presence now as the timer will do the first execution after the x amount of seconds we specify */ - bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_game, "with " + std::to_string(dpp::get_guild_cache()->count()) + " guilds!")); - - /* Create a timer that runs every 120 seconds, that sets the status */ - bot.start_timer([&bot](const dpp::timer& timer) { - bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_game, "with " + std::to_string(dpp::get_guild_cache()->count()) + " guilds!")); - }, 120); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} setting_status2.cpp If you followed that well, your bot should now say this on members list! From 97c59df63593b704fca9caa755c24a83e57eab51 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Sep 2023 00:14:47 +0000 Subject: [PATCH 07/13] docs: interactions and components examples split --- docpages/example_code/commandhandler.cpp | 53 +++++ docpages/example_code/components.cpp | 57 ++++++ docpages/example_code/components2.cpp | 62 ++++++ docpages/example_code/components3.cpp | 57 ++++++ docpages/example_code/context_menus.cpp | 39 ++++ docpages/example_code/detecting_messages.cpp | 24 +++ docpages/example_code/ephemeral.cpp | 32 +++ docpages/example_code/making_threads1.cpp | 36 ++++ docpages/example_code/making_threads2.cpp | 57 ++++++ .../modal_dialog_interactions.cpp | 73 +++++++ docpages/example_code/private_messaging.cpp | 66 ++++++ docpages/example_code/slashcommands1.cpp | 47 +++++ docpages/example_code/slashcommands2.cpp | 47 +++++ docpages/example_code/slashcommands3.cpp | 41 ++++ docpages/example_code/slashcommands4.cpp | 42 ++++ docpages/example_code/subcommands.cpp | 72 +++++++ docpages/example_code/upload_parameter.cpp | 43 ++++ .../commandhandler.md | 57 +----- .../interactions_and_components/components.md | 60 +----- .../components2.md | 65 +----- .../components3.md | 60 +----- .../context_menus.md | 42 +--- .../detecting-messages.md | 27 +-- .../making_threads.md | 98 +-------- .../modal_dialog_interactions.md | 76 +------ .../private-messaging.md | 69 +------ .../slashcommands.md | 189 +----------------- .../subcommands.md | 75 +------ .../upload_parameter.md | 46 +---- .../user-only-messages.md | 35 +--- 30 files changed, 865 insertions(+), 882 deletions(-) create mode 100644 docpages/example_code/commandhandler.cpp create mode 100644 docpages/example_code/components.cpp create mode 100644 docpages/example_code/components2.cpp create mode 100644 docpages/example_code/components3.cpp create mode 100644 docpages/example_code/context_menus.cpp create mode 100644 docpages/example_code/detecting_messages.cpp create mode 100644 docpages/example_code/ephemeral.cpp create mode 100644 docpages/example_code/making_threads1.cpp create mode 100644 docpages/example_code/making_threads2.cpp create mode 100644 docpages/example_code/modal_dialog_interactions.cpp create mode 100644 docpages/example_code/private_messaging.cpp create mode 100644 docpages/example_code/slashcommands1.cpp create mode 100644 docpages/example_code/slashcommands2.cpp create mode 100644 docpages/example_code/slashcommands3.cpp create mode 100644 docpages/example_code/slashcommands4.cpp create mode 100644 docpages/example_code/subcommands.cpp create mode 100644 docpages/example_code/upload_parameter.cpp diff --git a/docpages/example_code/commandhandler.cpp b/docpages/example_code/commandhandler.cpp new file mode 100644 index 0000000000..ce7c6fb378 --- /dev/null +++ b/docpages/example_code/commandhandler.cpp @@ -0,0 +1,53 @@ +#include + +int main() +{ + /* If your bot only uses the "/" prefix, you can remove the intents here. */ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); + + bot.on_log(dpp::utility::cout_logger()); + + /* Create command handler, and specify prefixes */ + dpp::commandhandler command_handler(&bot); + /* Specifying a prefix of "/" tells the command handler it should also expect slash commands. Remove the .add_prefix(".") if you wish to only make it a slash command */ + command_handler.add_prefix(".").add_prefix("/"); + + bot.on_ready([&command_handler](const dpp::ready_t &event) { + + command_handler.add_command( + /* Command name */ + "ping", + + /* Parameters */ + { + {"testparameter", dpp::param_info(dpp::pt_string, true, "Optional test parameter") } + }, + + /* Command handler */ + [&command_handler](const std::string& command, const dpp::parameter_list_t& parameters, dpp::command_source src) { + std::string got_param; + if (!parameters.empty()) { + got_param = std::get(parameters[0].second); + } + command_handler.reply(dpp::message("Pong! -> " + got_param), src); + }, + + /* Command description */ + "A test ping command", + + /* Guild id (omit for a guild command) */ + 819556414099554344 + ); + + /* NOTE: We must call this to ensure slash commands are registered. + * This does a bulk register, which will replace other commands + * that are registered already! + */ + command_handler.register_commands(); + + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/components.cpp b/docpages/example_code/components.cpp new file mode 100644 index 0000000000..5caa90b23b --- /dev/null +++ b/docpages/example_code/components.cpp @@ -0,0 +1,57 @@ +#include +#include + +int main() { + + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "button") { + + /* Create a message */ + dpp::message msg(event.command.channel_id, "this text has a button"); + + /* Add an action row, and then a button within the action row. */ + msg.add_component( + dpp::component().add_component( + dpp::component(). + set_label("Click me!"). + set_type(dpp::cot_button). + set_emoji(dpp::unicode_emoji::smile). + set_style(dpp::cos_danger). + set_id("myid") + ) + ); + + /* Reply to the user with our message. */ + event.reply(msg); + } + }); + + /* When a user clicks your button, the on_button_click event will fire, + * containing the custom_id you defined in your button. + */ + bot.on_button_click([&bot](const dpp::button_click_t& event) { + /* Button clicks are still interactions, and must be replied to in some form to + * prevent the "this interaction has failed" message from Discord to the user. + */ + event.reply("You clicked: " + event.custom_id); + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand("button", "Send a message with a button!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/components2.cpp b/docpages/example_code/components2.cpp new file mode 100644 index 0000000000..d5d610bf20 --- /dev/null +++ b/docpages/example_code/components2.cpp @@ -0,0 +1,62 @@ +#include + +int main() { + + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "math") { + + /* Create a message */ + dpp::message msg(event.command.channel_id, "What is 5+5?"); + + /* Add an action row, and then 3 buttons within the action row. */ + msg.add_component( + dpp::component().add_component( + dpp::component(). + set_label("9"). + set_style(dpp::cos_primary). + set_id("9") + ).add_component( + dpp::component(). + set_label("10"). + set_style(dpp::cos_primary). + set_id("10") + ).add_component( + dpp::component(). + set_label("11"). + set_style(dpp::cos_primary). + set_id("11") + ) + ); + + /* Reply to the user with our message. */ + event.reply(msg); + } + }); + + bot.on_button_click([&bot](const dpp::button_click_t & event) { + if (event.custom_id == "10") { + event.reply(dpp::message("You got it right!").set_flags(dpp::m_ephemeral)); + } else { + event.reply(dpp::message("Wrong! Try again.").set_flags(dpp::m_ephemeral)); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand("math", "A quick maths question!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/components3.cpp b/docpages/example_code/components3.cpp new file mode 100644 index 0000000000..b69d48899d --- /dev/null +++ b/docpages/example_code/components3.cpp @@ -0,0 +1,57 @@ +#include +#include + +int main() { + + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "select") { + + /* Create a message */ + dpp::message msg(event.command.channel_id, "This text has a select menu!"); + + /* Add an action row, and a select menu within the action row. */ + msg.add_component( + dpp::component().add_component( + dpp::component(). + set_type(dpp::cot_selectmenu). + set_placeholder("Pick something"). + add_select_option(dpp::select_option("label1","value1","description1").set_emoji(dpp::unicode_emoji::smile)). + add_select_option(dpp::select_option("label2","value2","description2").set_emoji(dpp::unicode_emoji::slight_smile)). + set_id("myselectid") + ) + ); + + /* Reply to the user with our message. */ + event.reply(msg); + } + }); + + /* When a user clicks your select menu , the on_select_click event will fire, + * containing the custom_id you defined in your select menu. + */ + bot.on_select_click([&bot](const dpp::select_click_t & event) { + /* Select clicks are still interactions, and must be replied to in some form to + * prevent the "this interaction has failed" message from Discord to the user. + */ + event.reply("You clicked " + event.custom_id + " and chose: " + event.values[0]); + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Create and register a command when the bot is ready */ + bot.global_command_create(dpp::slashcommand("select", "Select something at random!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/context_menus.cpp b/docpages/example_code/context_menus.cpp new file mode 100644 index 0000000000..ff7a32a555 --- /dev/null +++ b/docpages/example_code/context_menus.cpp @@ -0,0 +1,39 @@ +#include +#include + +int main() +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* Use the on_user_context_menu event to look for user context menu actions */ + bot.on_user_context_menu([&](const dpp::user_context_menu_t& event) { + + /* check if the context menu name is High Five */ + if (event.command.get_command_name() == "high five") { + dpp::user user = event.get_user(); // the user who the command has been issued on + dpp::user author = event.command.get_issuing_user(); // the user who clicked on the context menu + event.reply(author.get_mention() + " slapped " + user.get_mention()); + } + }); + + bot.on_ready([&bot](const dpp::ready_t &event) { + if (dpp::run_once()) { + + /* Create the command */ + dpp::slashcommand command; + command.set_name("High Five"); + command.set_application_id(bot.me.id); + command.set_type(dpp::ctxm_user); + + /* Register the command */ + bot.guild_command_create(command, 857692897221033129); /* Replace this with the guild id you want */ + } + }); + + /* Start bot */ + bot.start(dpp::st_wait); + + return 0; +} \ No newline at end of file diff --git a/docpages/example_code/detecting_messages.cpp b/docpages/example_code/detecting_messages.cpp new file mode 100644 index 0000000000..ecb0805230 --- /dev/null +++ b/docpages/example_code/detecting_messages.cpp @@ -0,0 +1,24 @@ +#include + +int main() +{ + /* Create the bot, but with our intents so we can use messages. */ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when the bot detects a message in any server and any channel it has access to. */ + bot.on_message_create([&bot](const dpp::message_create_t& event) { + + /* See if the message contains the phrase we want to check for. + * If there's at least a single match, we reply and say it's not allowed. + */ + if (event.msg.content.find("bad word") != std::string::npos) { + event.reply("That is not allowed here. Please, mind your language!", true); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} \ No newline at end of file diff --git a/docpages/example_code/ephemeral.cpp b/docpages/example_code/ephemeral.cpp new file mode 100644 index 0000000000..5fb4f7dc0c --- /dev/null +++ b/docpages/example_code/ephemeral.cpp @@ -0,0 +1,32 @@ +#include + +int main() +{ + /* Create the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "hello") { + + /* Reply to the user, but only let them see the response. */ + event.reply(dpp::message("Hello! How are you today?").set_flags(dpp::m_ephemeral)); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create and Register the command */ + bot.global_command_create(dpp::slashcommand("hello", "Hello there!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/making_threads1.cpp b/docpages/example_code/making_threads1.cpp new file mode 100644 index 0000000000..36f6db627d --- /dev/null +++ b/docpages/example_code/making_threads1.cpp @@ -0,0 +1,36 @@ +#include + +int main() +{ + /* Create the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when the bot detects a message in any server and any channel it has access to. */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ + if (event.command.get_command_name() == "create-thread") { + /* Here we create a thread in the current channel. It will expire after 60 minutes of inactivity. We'll also allow other mods to join, and we won't add a slowdown timer. */ + bot.thread_create("Cool thread!", event.command.channel_id, 60, dpp::channel_type::CHANNEL_PUBLIC_THREAD, true, 0, [event](const dpp::confirmation_callback_t& callback) { + if (callback.is_error()) { + event.reply("Failed to create a thread!"); + return; + } + + event.reply("Created a thread for you!"); + }); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + /* Create and register the command */ + bot.global_command_create(dpp::slashcommand("create-thread", "Create a thread!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/making_threads2.cpp b/docpages/example_code/making_threads2.cpp new file mode 100644 index 0000000000..21037ff233 --- /dev/null +++ b/docpages/example_code/making_threads2.cpp @@ -0,0 +1,57 @@ +#include + +int main() +{ + /* Create the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when the bot detects a message in any server and any channel it has access to. */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + /* Check which command they ran */ + if (event.command.get_command_name() == "message-thread") { + /* Get all active threads in a guild. */ + bot.threads_get_active(event.command.guild_id, [&bot, event](const dpp::confirmation_callback_t& callback) { + if (callback.is_error()) { + event.reply("Failed to get threads!"); + return; + } + + /* Get the list of active threads in the guild. */ + auto threads = callback.get(); + + dpp::snowflake thread_id; + + /* Loop through the threads, getting each value in the map. Then we get the first value and then break off. + * The reason we're getting only the first value is because, for this example, we'll just assume you've only got a single active thread (the one created by the bot) + */ + for (const auto& _thread : threads) { + thread_id = _thread.first; + break; + } + + /* Send a message in the first thread we find. */ + bot.message_create(dpp::message(thread_id, "Hey, I'm first to message in a cool thread!"), [event](const dpp::confirmation_callback_t& callback2) { + if (callback2.is_error()) { + event.reply("Failed to send a message in a thread."); + return; + } + + event.reply("I've sent a message in the specified thread."); + }); + }); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + /* Create and register the command */ + bot.global_command_create(dpp::slashcommand("message-thread", "Message a thread!", bot.me.id)); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/modal_dialog_interactions.cpp b/docpages/example_code/modal_dialog_interactions.cpp new file mode 100644 index 0000000000..764581d468 --- /dev/null +++ b/docpages/example_code/modal_dialog_interactions.cpp @@ -0,0 +1,73 @@ +#include +#include + +int main(int argc, char const *argv[]) +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check for our /dialog command */ + if (event.command.get_command_name() == "dialog") { + + /* Instantiate an interaction_modal_response object */ + dpp::interaction_modal_response modal("my_modal", "Please enter stuff"); + + /* Add a text component */ + modal.add_component( + dpp::component(). + set_label("Short type rammel"). + set_id("field_id"). + set_type(dpp::cot_text). + set_placeholder("gumd"). + set_min_length(5). + set_max_length(50). + set_text_style(dpp::text_short) + ); + + /* Add another text component in the next row, as required by Discord */ + modal.add_row(); + modal.add_component( + dpp::component(). + set_label("Type rammel"). + set_id("field_id2"). + set_type(dpp::cot_text). + set_placeholder("gumf"). + set_min_length(1). + set_max_length(2000). + set_text_style(dpp::text_paragraph) + ); + + /* Trigger the dialog box. All dialog boxes are ephemeral */ + event.dialog(modal); + } + }); + + /* This event handles form submission for the modal dialog we create above */ + bot.on_form_submit([&](const dpp::form_submit_t & event) { + + /* For this simple example we know the first element of the first row ([0][0]) is value type string. + * In the real world it may not be safe to make such assumptions! + */ + std::string v = std::get(event.components[0].components[0].value); + + dpp::message m; + m.set_content("You entered: " + v).set_flags(dpp::m_ephemeral); + + /* Emit a reply. Form submission is still an interaction and must generate some form of reply! */ + event.reply(m); + }); + + bot.on_ready([&](const dpp::ready_t & event) { + if (dpp::run_once()) { + /* Create a slash command and register it as a global command */ + bot.global_command_create(dpp::slashcommand("dialog", "Make a modal dialog box", bot.me.id)); + } + }); + + /* Start bot */ + + bot.start(dpp::st_wait); + return 0; +} diff --git a/docpages/example_code/private_messaging.cpp b/docpages/example_code/private_messaging.cpp new file mode 100644 index 0000000000..3ec2a38b20 --- /dev/null +++ b/docpages/example_code/private_messaging.cpp @@ -0,0 +1,66 @@ +#include + +int main() +{ + /* Create the bot */ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "pm") { + + dpp::snowflake user; + + /* If there was no specified user, we set the "user" variable to the command author (issuing user). */ + if (event.get_parameter("user").index() == 0) { + user = event.command.get_issuing_user().id; + } else { /* Otherwise, we set it to the specified user! */ + user = std::get(event.get_parameter("user")); + } + + /* Send a message to the user set above. */ + bot.direct_message_create(user, dpp::message("Here's a private message!"), [event, user](const dpp::confirmation_callback_t& callback){ + /* If the callback errors, we want to send a message telling the author that something went wrong. */ + if (callback.is_error()) { + /* Here, we want the error message to be different if the user we're trying to send a message to is the command author. */ + if (user == event.command.get_issuing_user().id) { + event.reply(dpp::message("I couldn't send you a message.").set_flags(dpp::m_ephemeral)); + } else { + event.reply(dpp::message("I couldn't send a message to that user. Please check that is a valid user!").set_flags(dpp::m_ephemeral)); + } + + return; + } + + /* We do the same here, so the message is different if it's to the command author or if it's to a specified user. */ + if (user == event.command.get_issuing_user().id) { + event.reply(dpp::message("I've sent you a private message.").set_flags(dpp::m_ephemeral)); + } else { + event.reply(dpp::message("I've sent a message to that user.").set_flags(dpp::m_ephemeral)); + } + }); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) { + if (dpp::run_once()) { + + /* Register the command */ + dpp::slashcommand command("pm", "Send a private message.", bot.me.id); + + /* Add the option for a user mention that isn't required */ + command.add_option(dpp::command_option(dpp::co_mentionable, "user", "The user to message", false)); + + /* Register the command */ + bot.global_command_create(command); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/slashcommands1.cpp b/docpages/example_code/slashcommands1.cpp new file mode 100644 index 0000000000..0f5f4bbf9c --- /dev/null +++ b/docpages/example_code/slashcommands1.cpp @@ -0,0 +1,47 @@ +#include + +int main() +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "blep") { + + /* Fetch a parameter value from the command parameters */ + std::string animal = std::get(event.get_parameter("animal")); + + /* Reply to the command. There is an overloaded version of this + * call that accepts a dpp::message so you can send embeds. + */ + event.reply(std::string("Blep! You chose") + animal); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new global command on ready event */ + dpp::slashcommand newcommand("blep", "Send a random adorable animal photo", bot.me.id); + newcommand.add_option( + dpp::command_option(dpp::co_string, "animal", "The type of animal", true). + add_choice(dpp::command_option_choice("Dog", std::string("animal_dog"))). + add_choice(dpp::command_option_choice("Cat", std::string("animal_cat"))). + add_choice(dpp::command_option_choice("Penguin", std::string("animal_penguin") + ) + ) + ); + + /* Register the command */ + bot.global_command_create(newcommand); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/slashcommands2.cpp b/docpages/example_code/slashcommands2.cpp new file mode 100644 index 0000000000..1eb9c4159e --- /dev/null +++ b/docpages/example_code/slashcommands2.cpp @@ -0,0 +1,47 @@ +#include + +int main() +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "blep") { + + /* Fetch a parameter value from the command parameters */ + std::string animal = std::get(event.get_parameter("animal")); + + /* Reply to the command. There is an overloaded version of this + * call that accepts a dpp::message so you can send embeds. + */ + event.reply(std::string("Blep! You chose") + animal); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new global command on ready event */ + dpp::slashcommand newcommand("blep", "Send a random adorable animal photo", bot.me.id); + newcommand.add_option( + dpp::command_option(dpp::co_string, "animal", "The type of animal", true). + add_choice(dpp::command_option_choice("Dog", std::string("animal_dog"))). + add_choice(dpp::command_option_choice("Cat", std::string("animal_cat"))). + add_choice(dpp::command_option_choice("Penguin", std::string("animal_penguin") + ) + ) + ); + + /* Register the command */ + bot.guild_command_create(newcommand, 857692897221033129); /* Replace this with the guild id you want */ + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/slashcommands3.cpp b/docpages/example_code/slashcommands3.cpp new file mode 100644 index 0000000000..578d3c3814 --- /dev/null +++ b/docpages/example_code/slashcommands3.cpp @@ -0,0 +1,41 @@ +#include + +int main() +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "ping") { + event.reply("Pong!"); + } else if (event.command.get_command_name() == "pong") { + event.reply("Ping!"); + } else if (event.command.get_command_name() == "ding") { + event.reply("Dong!"); + } else if (event.command.get_command_name() == "dong") { + event.reply("Ding!"); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create some commands */ + dpp::slashcommand pingcommand("ping", "Pong!", bot.me.id); + dpp::slashcommand pongcommand("pong", "Ping!", bot.me.id); + dpp::slashcommand dingcommand("ding", "Dong!", bot.me.id); + dpp::slashcommand dongcommand("dong", "Ding!", bot.me.id); + + /* Register our commands in a list using bulk */ + bot.global_bulk_command_create({ pingcommand, pongcommand, dingcommand, dongcommand }); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/slashcommands4.cpp b/docpages/example_code/slashcommands4.cpp new file mode 100644 index 0000000000..3f10950d6f --- /dev/null +++ b/docpages/example_code/slashcommands4.cpp @@ -0,0 +1,42 @@ +#include + +int main() +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "ping") { + event.reply("Pong!"); + } else if (event.command.get_command_name() == "pong") { + event.reply("Ping!"); + } else if (event.command.get_command_name() == "ding") { + event.reply("Dong!"); + } else if (event.command.get_command_name() == "dong") { + event.reply("Ding!"); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create some commands */ + dpp::slashcommand pingcommand("ping", "Pong!", bot.me.id); + dpp::slashcommand pongcommand("pong", "Ping!", bot.me.id); + dpp::slashcommand dingcommand("ding", "Dong!", bot.me.id); + dpp::slashcommand dongcommand("dong", "Ding!", bot.me.id); + + /* Register our commands in a list using bulk */ + bot.guild_bulk_command_create({ pingcommand, pongcommand, dingcommand, dongcommand }, 857692897221033129); + + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/subcommands.cpp b/docpages/example_code/subcommands.cpp new file mode 100644 index 0000000000..ac8c65da18 --- /dev/null +++ b/docpages/example_code/subcommands.cpp @@ -0,0 +1,72 @@ +#include +#include + +int main() { + + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* Executes on ready. */ + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + /* Define a slash command. */ + dpp::slashcommand image("image", "Send a specific image.", bot.me.id); + image.add_option( + /* Create a subcommand type option for "dog". */ + dpp::command_option(dpp::co_sub_command, "dog", "Send a picture of a dog."). + add_option(dpp::command_option(dpp::co_user, "user", "User to turn into a dog.", false)) + ); + image.add_option( + /* Create another subcommand type option for "cat". */ + dpp::command_option(dpp::co_sub_command, "cat", "Send a picture of a cat."). + add_option(dpp::command_option(dpp::co_user, "user", "User to turn into a cat.", false)) + ); + /* Create command */ + bot.global_command_create(image); + } + }); + + /* Use the on_slashcommand event to look for commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + dpp::interaction interaction = event.command; + dpp::command_interaction cmd_data = interaction.get_command_interaction(); + /* Check if the command is the image command. */ + if (interaction.get_command_name() == "image") { + /* Get the sub command */ + auto subcommand = cmd_data.options[0]; + /* Check if the subcommand is "dog" */ + if (subcommand.name == "dog") { + /* Checks if the subcommand has any options. */ + if (!subcommand.options.empty()) { + /* Get the user from the parameter */ + dpp::user user = interaction.get_resolved_user( + subcommand.get_value(0) + ); + event.reply(user.get_mention() + " has now been turned into a dog."); + } else { + /* Reply if there were no options.. */ + event.reply("No user specified"); + } + } + /* Check if the subcommand is "cat" */ + if (subcommand.name == "cat") { + /* Checks if the subcommand has any options. */ + if (!subcommand.options.empty()) { + /* Get the user from the parameter */ + dpp::user user = interaction.get_resolved_user( + subcommand.get_value(0) + ); + event.reply(user.get_mention() + " has now been turned into a cat."); + } else { + /* Reply if there were no options.. */ + event.reply("No user specified"); + } + } + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_code/upload_parameter.cpp b/docpages/example_code/upload_parameter.cpp new file mode 100644 index 0000000000..dc78829c44 --- /dev/null +++ b/docpages/example_code/upload_parameter.cpp @@ -0,0 +1,43 @@ +#include + +int main() +{ + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when someone issues your commands */ + bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + + /* Check which command they ran */ + if (event.command.get_command_name() == "show") { + + /* Get the file id from the parameter attachment. */ + dpp::snowflake file_id = std::get(event.get_parameter("file")); + + /* Get the attachment that the user inputted from the file id. */ + dpp::attachment att = event.command.get_resolved_attachment(file_id); + + /* Reply with the file as a URL. */ + event.reply(att.url); + } + }); + + bot.on_ready([&bot](const dpp::ready_t & event) { + if (dpp::run_once()) { + + /* Create a new command. */ + dpp::slashcommand newcommand("show", "Show an uploaded file", bot.me.id); + + /* Add a parameter option. */ + newcommand.add_option(dpp::command_option(dpp::co_attachment, "file", "Select an image")); + + /* Register the command */ + bot.global_command_create(newcommand); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} diff --git a/docpages/example_programs/interactions_and_components/commandhandler.md b/docpages/example_programs/interactions_and_components/commandhandler.md index 140fc9aab3..69b84caf6b 100644 --- a/docpages/example_programs/interactions_and_components/commandhandler.md +++ b/docpages/example_programs/interactions_and_components/commandhandler.md @@ -12,59 +12,4 @@ and allows you to decide how and where commands should be routed, either to an o \warning As of [August 30th, 2022](https://support-dev.discord.com/hc/en-us/articles/6025578854295-Why-We-Moved-to-Slash-Commands), you are advised to only be using slash commands, not messages for commands. To prevent the command handler from handling commands with messages, you should only use the "/" prefix. If you wish to still use messages for commands, this tutorial will still cover it but, again, it is discouraged by Discord. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() -{ - /* If your bot only uses the "/" prefix, you can remove the intents here. */ - dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); - - bot.on_log(dpp::utility::cout_logger()); - - /* Create command handler, and specify prefixes */ - dpp::commandhandler command_handler(&bot); - /* Specifying a prefix of "/" tells the command handler it should also expect slash commands. Remove the .add_prefix(".") if you wish to only make it a slash command */ - command_handler.add_prefix(".").add_prefix("/"); - - bot.on_ready([&command_handler](const dpp::ready_t &event) { - - command_handler.add_command( - /* Command name */ - "ping", - - /* Parameters */ - { - {"testparameter", dpp::param_info(dpp::pt_string, true, "Optional test parameter") } - }, - - /* Command handler */ - [&command_handler](const std::string& command, const dpp::parameter_list_t& parameters, dpp::command_source src) { - std::string got_param; - if (!parameters.empty()) { - got_param = std::get(parameters[0].second); - } - command_handler.reply(dpp::message("Pong! -> " + got_param), src); - }, - - /* Command description */ - "A test ping command", - - /* Guild id (omit for a guild command) */ - 819556414099554344 - ); - - /* NOTE: We must call this to ensure slash commands are registered. - * This does a bulk register, which will replace other commands - * that are registered already! - */ - command_handler.register_commands(); - - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +\include{cpp} commandhandler.cpp diff --git a/docpages/example_programs/interactions_and_components/components.md b/docpages/example_programs/interactions_and_components/components.md index 90eeca928a..dfadf95004 100644 --- a/docpages/example_programs/interactions_and_components/components.md +++ b/docpages/example_programs/interactions_and_components/components.md @@ -3,65 +3,7 @@ Discord's newest features support sending buttons alongside messages, which when clicked by the user trigger an interaction which is routed by D++ as an `on_button_click` event. To make use of this, use this code as in this example. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include -#include - -int main() { - - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "button") { - - /* Create a message */ - dpp::message msg(event.command.channel_id, "this text has a button"); - - /* Add an action row, and then a button within the action row. */ - msg.add_component( - dpp::component().add_component( - dpp::component(). - set_label("Click me!"). - set_type(dpp::cot_button). - set_emoji(dpp::unicode_emoji::smile). - set_style(dpp::cos_danger). - set_id("myid") - ) - ); - - /* Reply to the user with our message. */ - event.reply(msg); - } - }); - - /* When a user clicks your button, the on_button_click event will fire, - * containing the custom_id you defined in your button. - */ - bot.on_button_click([&bot](const dpp::button_click_t& event) { - /* Button clicks are still interactions, and must be replied to in some form to - * prevent the "this interaction has failed" message from Discord to the user. - */ - event.reply("You clicked: " + event.custom_id); - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand("button", "Send a message with a button!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} components.cpp When the feature is functioning, the code below will produce buttons on the reply message like in the image below: diff --git a/docpages/example_programs/interactions_and_components/components2.md b/docpages/example_programs/interactions_and_components/components2.md index 1bc4ae8599..5d306ac47c 100644 --- a/docpages/example_programs/interactions_and_components/components2.md +++ b/docpages/example_programs/interactions_and_components/components2.md @@ -2,70 +2,7 @@ This example demonstrates adding multiple buttons, receiving button clicks and sending response messages. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() { - - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "math") { - - /* Create a message */ - dpp::message msg(event.command.channel_id, "What is 5+5?"); - - /* Add an action row, and then 3 buttons within the action row. */ - msg.add_component( - dpp::component().add_component( - dpp::component(). - set_label("9"). - set_style(dpp::cos_primary). - set_id("9") - ).add_component( - dpp::component(). - set_label("10"). - set_style(dpp::cos_primary). - set_id("10") - ).add_component( - dpp::component(). - set_label("11"). - set_style(dpp::cos_primary). - set_id("11") - ) - ); - - /* Reply to the user with our message. */ - event.reply(msg); - } - }); - - bot.on_button_click([&bot](const dpp::button_click_t & event) { - if (event.custom_id == "10") { - event.reply(dpp::message("You got it right!").set_flags(dpp::m_ephemeral)); - } else { - event.reply(dpp::message("Wrong! Try again.").set_flags(dpp::m_ephemeral)); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand("math", "A quick maths question!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} components2.cpp This code will send a different message for correct and incorrect answers. diff --git a/docpages/example_programs/interactions_and_components/components3.md b/docpages/example_programs/interactions_and_components/components3.md index 359a101c6b..e3ac023c23 100644 --- a/docpages/example_programs/interactions_and_components/components3.md +++ b/docpages/example_programs/interactions_and_components/components3.md @@ -2,62 +2,4 @@ This example demonstrates creating a select menu, receiving select menu clicks and sending a response message. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include -#include - -int main() { - - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "select") { - - /* Create a message */ - dpp::message msg(event.command.channel_id, "This text has a select menu!"); - - /* Add an action row, and a select menu within the action row. */ - msg.add_component( - dpp::component().add_component( - dpp::component(). - set_type(dpp::cot_selectmenu). - set_placeholder("Pick something"). - add_select_option(dpp::select_option("label1","value1","description1").set_emoji(dpp::unicode_emoji::smile)). - add_select_option(dpp::select_option("label2","value2","description2").set_emoji(dpp::unicode_emoji::slight_smile)). - set_id("myselectid") - ) - ); - - /* Reply to the user with our message. */ - event.reply(msg); - } - }); - - /* When a user clicks your select menu , the on_select_click event will fire, - * containing the custom_id you defined in your select menu. - */ - bot.on_select_click([&bot](const dpp::select_click_t & event) { - /* Select clicks are still interactions, and must be replied to in some form to - * prevent the "this interaction has failed" message from Discord to the user. - */ - event.reply("You clicked " + event.custom_id + " and chose: " + event.values[0]); - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Create and register a command when the bot is ready */ - bot.global_command_create(dpp::slashcommand("select", "Select something at random!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} components3.cpp diff --git a/docpages/example_programs/interactions_and_components/context_menus.md b/docpages/example_programs/interactions_and_components/context_menus.md index d810fcc0a0..cd451998d6 100644 --- a/docpages/example_programs/interactions_and_components/context_menus.md +++ b/docpages/example_programs/interactions_and_components/context_menus.md @@ -8,47 +8,7 @@ Context menus are application commands that appear on the context menu (right cl The following example shows how to create and handle **user context menus** for message context menus, read the notice above. -~~~~~~~~~~{.cpp} -#include -#include - -int main() -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* Use the on_user_context_menu event to look for user context menu actions */ - bot.on_user_context_menu([&](const dpp::user_context_menu_t& event) { - - /* check if the context menu name is High Five */ - if (event.command.get_command_name() == "high five") { - dpp::user user = event.get_user(); // the user who the command has been issued on - dpp::user author = event.command.get_issuing_user(); // the user who clicked on the context menu - event.reply(author.get_mention() + " slapped " + user.get_mention()); - } - }); - - bot.on_ready([&bot](const dpp::ready_t &event) { - if (dpp::run_once()) { - - /* Create the command */ - dpp::slashcommand command; - command.set_name("High Five"); - command.set_application_id(bot.me.id); - command.set_type(dpp::ctxm_user); - - /* Register the command */ - bot.guild_command_create(command, 857692897221033129); /* Replace this with the guild id you want */ - } - }); - - /* Start bot */ - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} context_menus.cpp It registers a guild command that can be called by right-clicking a user and clicking on the created menu. diff --git a/docpages/example_programs/interactions_and_components/detecting-messages.md b/docpages/example_programs/interactions_and_components/detecting-messages.md index a3278c85a5..2964f29ea3 100644 --- a/docpages/example_programs/interactions_and_components/detecting-messages.md +++ b/docpages/example_programs/interactions_and_components/detecting-messages.md @@ -4,32 +4,7 @@ Sometimes, you may want to listen out for a message, rather than a command. This \warning As of August 30th, 2022, Discord made Message Content a privileged intent. Whilst this means you can still use prefixed messages as commands, Discord does not encourage this and heavily suggests you use \ref slashcommands "slash commands". If you wish to create commands, use \ref slashcommands "slash commands", not messages. -~~~~~~~~~~{.cpp} -#include - -int main() -{ - /* Create the bot, but with our intents so we can use messages. */ - dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when the bot detects a message in any server and any channel it has access to. */ - bot.on_message_create([&bot](const dpp::message_create_t& event) { - - /* See if the message contains the phrase we want to check for. - * If there's at least a single match, we reply and say it's not allowed. - */ - if (event.msg.content.find("bad word") != std::string::npos) { - event.reply("That is not allowed here. Please, mind your language!", true); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} detecting_messages.cpp If all went well, you should have something like this! diff --git a/docpages/example_programs/interactions_and_components/making_threads.md b/docpages/example_programs/interactions_and_components/making_threads.md index 25822e0ba6..ee24e614d6 100644 --- a/docpages/example_programs/interactions_and_components/making_threads.md +++ b/docpages/example_programs/interactions_and_components/making_threads.md @@ -5,110 +5,16 @@ A new feature added to Discord recently is `Threads`, these allow you to break o In this tutorial, we'll be going through how to create a thread and how to talk in a thread. First, let's go through creating a thread. -~~~~~~~~~~{.cpp} -#include -int main() -{ - /* Create the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when the bot detects a message in any server and any channel it has access to. */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - /* Check which command they ran */ - if (event.command.get_command_name() == "create-thread") { - /* Here we create a thread in the current channel. It will expire after 60 minutes of inactivity. We'll also allow other mods to join, and we won't add a slowdown timer. */ - bot.thread_create("Cool thread!", event.command.channel_id, 60, dpp::channel_type::CHANNEL_PUBLIC_THREAD, true, 0, [event](const dpp::confirmation_callback_t& callback) { - if (callback.is_error()) { - event.reply("Failed to create a thread!"); - return; - } - - event.reply("Created a thread for you!"); - }); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - /* Create and register the command */ - bot.global_command_create(dpp::slashcommand("create-thread", "Create a thread!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} making_threads1.cpp If all went well, you'll see that the bot has successfully created a thread! \image html creating_thread.png Now, let's cover talking in that thread from a channel. It's worth noting that we will be assuming that the thread you just created is the only thread in your server! -~~~~~~~~~~{.cpp} - -#include - -int main() -{ - /* Create the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when the bot detects a message in any server and any channel it has access to. */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - /* Check which command they ran */ - if (event.command.get_command_name() == "message-thread") { - /* Get all active threads in a guild. */ - bot.threads_get_active(event.command.guild_id, [&bot, event](const dpp::confirmation_callback_t& callback) { - if (callback.is_error()) { - event.reply("Failed to get threads!"); - return; - } - - /* Get the list of active threads in the guild. */ - auto threads = callback.get(); - - dpp::snowflake thread_id; - - /* Loop through the threads, getting each value in the map. Then we get the first value and then break off. - * The reason we're getting only the first value is because, for this example, we'll just assume you've only got a single active thread (the one created by the bot) - */ - for (const auto& _thread : threads) { - thread_id = _thread.first; - break; - } - - /* Send a message in the first thread we find. */ - bot.message_create(dpp::message(thread_id, "Hey, I'm first to message in a cool thread!"), [event](const dpp::confirmation_callback_t& callback2) { - if (callback2.is_error()) { - event.reply("Failed to send a message in a thread."); - return; - } - - event.reply("I've sent a message in the specified thread."); - }); - }); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - /* Create and register the command */ - bot.global_command_create(dpp::slashcommand("message-thread", "Message a thread!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - return 0; -} -~~~~~~~~~~ +\include{cpp} making_threads2.cpp After that, you'll be able to see your bot send a message in your thread! diff --git a/docpages/example_programs/interactions_and_components/modal_dialog_interactions.md b/docpages/example_programs/interactions_and_components/modal_dialog_interactions.md index f9a99bcde6..bb63048202 100644 --- a/docpages/example_programs/interactions_and_components/modal_dialog_interactions.md +++ b/docpages/example_programs/interactions_and_components/modal_dialog_interactions.md @@ -4,81 +4,7 @@ Modal dialog interactions are a new Discord API feature that allow you to have p Each dialog box may have up to five rows of input fields. The example below demonstrates a simple setup with just one text input: -~~~~~~~~~~{.cpp} -#include -#include - -int main(int argc, char const *argv[]) -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - /* Check for our /dialog command */ - if (event.command.get_command_name() == "dialog") { - - /* Instantiate an interaction_modal_response object */ - dpp::interaction_modal_response modal("my_modal", "Please enter stuff"); - - /* Add a text component */ - modal.add_component( - dpp::component(). - set_label("Short type rammel"). - set_id("field_id"). - set_type(dpp::cot_text). - set_placeholder("gumd"). - set_min_length(5). - set_max_length(50). - set_text_style(dpp::text_short) - ); - - /* Add another text component in the next row, as required by Discord */ - modal.add_row(); - modal.add_component( - dpp::component(). - set_label("Type rammel"). - set_id("field_id2"). - set_type(dpp::cot_text). - set_placeholder("gumf"). - set_min_length(1). - set_max_length(2000). - set_text_style(dpp::text_paragraph) - ); - - /* Trigger the dialog box. All dialog boxes are ephemeral */ - event.dialog(modal); - } - }); - - /* This event handles form submission for the modal dialog we create above */ - bot.on_form_submit([&](const dpp::form_submit_t & event) { - - /* For this simple example we know the first element of the first row ([0][0]) is value type string. - * In the real world it may not be safe to make such assumptions! - */ - std::string v = std::get(event.components[0].components[0].value); - - dpp::message m; - m.set_content("You entered: " + v).set_flags(dpp::m_ephemeral); - - /* Emit a reply. Form submission is still an interaction and must generate some form of reply! */ - event.reply(m); - }); - - bot.on_ready([&](const dpp::ready_t & event) { - if (dpp::run_once()) { - /* Create a slash command and register it as a global command */ - bot.global_command_create(dpp::slashcommand("dialog", "Make a modal dialog box", bot.me.id)); - } - }); - - /* Start bot */ - - bot.start(dpp::st_wait); - return 0; -} -~~~~~~~~~~ +\include{cpp} modal_dialog_interactions.cpp If you compile and run this program and wait for the global command to register, typing `/dialog` will present you with a dialog box like the one below: diff --git a/docpages/example_programs/interactions_and_components/private-messaging.md b/docpages/example_programs/interactions_and_components/private-messaging.md index 1d6c4e7037..9460449e8e 100644 --- a/docpages/example_programs/interactions_and_components/private-messaging.md +++ b/docpages/example_programs/interactions_and_components/private-messaging.md @@ -4,74 +4,7 @@ Sometimes it's simply not enough to ping someone in a server with a message, and \note This tutorial makes use of callbacks. For more information about that, visit \ref callback-functions "Using Callback Functions". -~~~~~~~~~~{.cpp} -#include - -int main() -{ - /* Create the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "pm") { - - dpp::snowflake user; - - /* If there was no specified user, we set the "user" variable to the command author (issuing user). */ - if (event.get_parameter("user").index() == 0) { - user = event.command.get_issuing_user().id; - } else { /* Otherwise, we set it to the specified user! */ - user = std::get(event.get_parameter("user")); - } - - /* Send a message to the user set above. */ - bot.direct_message_create(user, dpp::message("Here's a private message!"), [event, user](const dpp::confirmation_callback_t& callback){ - /* If the callback errors, we want to send a message telling the author that something went wrong. */ - if (callback.is_error()) { - /* Here, we want the error message to be different if the user we're trying to send a message to is the command author. */ - if (user == event.command.get_issuing_user().id) { - event.reply(dpp::message("I couldn't send you a message.").set_flags(dpp::m_ephemeral)); - } else { - event.reply(dpp::message("I couldn't send a message to that user. Please check that is a valid user!").set_flags(dpp::m_ephemeral)); - } - - return; - } - - /* We do the same here, so the message is different if it's to the command author or if it's to a specified user. */ - if (user == event.command.get_issuing_user().id) { - event.reply(dpp::message("I've sent you a private message.").set_flags(dpp::m_ephemeral)); - } else { - event.reply(dpp::message("I've sent a message to that user.").set_flags(dpp::m_ephemeral)); - } - }); - } - }); - - bot.on_ready([&bot](const dpp::ready_t& event) { - if (dpp::run_once()) { - - /* Register the command */ - dpp::slashcommand command("pm", "Send a private message.", bot.me.id); - - /* Add the option for a user mention that isn't required */ - command.add_option(dpp::command_option(dpp::co_mentionable, "user", "The user to message", false)); - - /* Register the command */ - bot.global_command_create(command); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} private_messaging.cpp That's it! Now, you should have something like this: diff --git a/docpages/example_programs/interactions_and_components/slashcommands.md b/docpages/example_programs/interactions_and_components/slashcommands.md index 57f3bac606..a6d7ea0bdc 100644 --- a/docpages/example_programs/interactions_and_components/slashcommands.md +++ b/docpages/example_programs/interactions_and_components/slashcommands.md @@ -12,199 +12,18 @@ dpp::interaction_create_t::reply has two overloaded versions of the method, one This first example goes over creating a single command globally. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "blep") { - - /* Fetch a parameter value from the command parameters */ - std::string animal = std::get(event.get_parameter("animal")); - - /* Reply to the command. There is an overloaded version of this - * call that accepts a dpp::message so you can send embeds. - */ - event.reply(std::string("Blep! You chose") + animal); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new global command on ready event */ - dpp::slashcommand newcommand("blep", "Send a random adorable animal photo", bot.me.id); - newcommand.add_option( - dpp::command_option(dpp::co_string, "animal", "The type of animal", true). - add_choice(dpp::command_option_choice("Dog", std::string("animal_dog"))). - add_choice(dpp::command_option_choice("Cat", std::string("animal_cat"))). - add_choice(dpp::command_option_choice("Penguin", std::string("animal_penguin") - ) - ) - ); - - /* Register the command */ - bot.global_command_create(newcommand); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} slashcommands1.cpp This second example goes over creating a single command but only for a guild, this means that the command can not be accessed anywhere else but the guild specified. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "blep") { - - /* Fetch a parameter value from the command parameters */ - std::string animal = std::get(event.get_parameter("animal")); - - /* Reply to the command. There is an overloaded version of this - * call that accepts a dpp::message so you can send embeds. - */ - event.reply(std::string("Blep! You chose") + animal); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new global command on ready event */ - dpp::slashcommand newcommand("blep", "Send a random adorable animal photo", bot.me.id); - newcommand.add_option( - dpp::command_option(dpp::co_string, "animal", "The type of animal", true). - add_choice(dpp::command_option_choice("Dog", std::string("animal_dog"))). - add_choice(dpp::command_option_choice("Cat", std::string("animal_cat"))). - add_choice(dpp::command_option_choice("Penguin", std::string("animal_penguin") - ) - ) - ); - - /* Register the command */ - bot.guild_command_create(newcommand, 857692897221033129); /* Replace this with the guild id you want */ - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} slashcommands2.cpp This third example goes over creating four commands globally, using the bulk create method. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "ping") { - event.reply("Pong!"); - } else if (event.command.get_command_name() == "pong") { - event.reply("Ping!"); - } else if (event.command.get_command_name() == "ding") { - event.reply("Dong!"); - } else if (event.command.get_command_name() == "dong") { - event.reply("Ding!"); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create some commands */ - dpp::slashcommand pingcommand("ping", "Pong!", bot.me.id); - dpp::slashcommand pongcommand("pong", "Ping!", bot.me.id); - dpp::slashcommand dingcommand("ding", "Dong!", bot.me.id); - dpp::slashcommand dongcommand("dong", "Ding!", bot.me.id); - - /* Register our commands in a list using bulk */ - bot.global_bulk_command_create({ pingcommand, pongcommand, dingcommand, dongcommand }); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} slashcommands3.cpp This fourth example goes over creating four commands but only for a guild. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "ping") { - event.reply("Pong!"); - } else if (event.command.get_command_name() == "pong") { - event.reply("Ping!"); - } else if (event.command.get_command_name() == "ding") { - event.reply("Dong!"); - } else if (event.command.get_command_name() == "dong") { - event.reply("Ding!"); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create some commands */ - dpp::slashcommand pingcommand("ping", "Pong!", bot.me.id); - dpp::slashcommand pongcommand("pong", "Ping!", bot.me.id); - dpp::slashcommand dingcommand("ding", "Dong!", bot.me.id); - dpp::slashcommand dongcommand("dong", "Ding!", bot.me.id); - - /* Register our commands in a list using bulk */ - bot.guild_bulk_command_create({ pingcommand, pongcommand, dingcommand, dongcommand }, 857692897221033129); - - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} slashcommands4.cpp \note For demonstration purposes, and small bots, this code is OK, but in the real world once your bot gets big, it's not recommended to create slash commands in the `on_ready` event even when it's inside dpp::run_once as, if you re-run your bot multiple times or start multiple clusters, you will quickly get rate-limited! You could, for example, add a commandline parameter to your bot (`argc`, `argv`) so that if you want the bot to register commands it must be launched with a specific command line argument. \ No newline at end of file diff --git a/docpages/example_programs/interactions_and_components/subcommands.md b/docpages/example_programs/interactions_and_components/subcommands.md index 250463332d..68755bd682 100644 --- a/docpages/example_programs/interactions_and_components/subcommands.md +++ b/docpages/example_programs/interactions_and_components/subcommands.md @@ -2,77 +2,4 @@ This demonstrates how to use sub-commands within slash commands. Also shown below is an example of how to get a "resolved" parameter without having to use the cache or an extra API call. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} -#include -#include - -int main() { - - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* Executes on ready. */ - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - /* Define a slash command. */ - dpp::slashcommand image("image", "Send a specific image.", bot.me.id); - image.add_option( - /* Create a subcommand type option for "dog". */ - dpp::command_option(dpp::co_sub_command, "dog", "Send a picture of a dog."). - add_option(dpp::command_option(dpp::co_user, "user", "User to turn into a dog.", false)) - ); - image.add_option( - /* Create another subcommand type option for "cat". */ - dpp::command_option(dpp::co_sub_command, "cat", "Send a picture of a cat."). - add_option(dpp::command_option(dpp::co_user, "user", "User to turn into a cat.", false)) - ); - /* Create command */ - bot.global_command_create(image); - } - }); - - /* Use the on_slashcommand event to look for commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - dpp::interaction interaction = event.command; - dpp::command_interaction cmd_data = interaction.get_command_interaction(); - /* Check if the command is the image command. */ - if (interaction.get_command_name() == "image") { - /* Get the sub command */ - auto subcommand = cmd_data.options[0]; - /* Check if the subcommand is "dog" */ - if (subcommand.name == "dog") { - /* Checks if the subcommand has any options. */ - if (!subcommand.options.empty()) { - /* Get the user from the parameter */ - dpp::user user = interaction.get_resolved_user( - subcommand.get_value(0) - ); - event.reply(user.get_mention() + " has now been turned into a dog."); - } else { - /* Reply if there were no options.. */ - event.reply("No user specified"); - } - } - /* Check if the subcommand is "cat" */ - if (subcommand.name == "cat") { - /* Checks if the subcommand has any options. */ - if (!subcommand.options.empty()) { - /* Get the user from the parameter */ - dpp::user user = interaction.get_resolved_user( - subcommand.get_value(0) - ); - event.reply(user.get_mention() + " has now been turned into a cat."); - } else { - /* Reply if there were no options.. */ - event.reply("No user specified"); - } - } - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\include{cpp} subcommands.cpp \ No newline at end of file diff --git a/docpages/example_programs/interactions_and_components/upload_parameter.md b/docpages/example_programs/interactions_and_components/upload_parameter.md index f971f20926..d73e878835 100644 --- a/docpages/example_programs/interactions_and_components/upload_parameter.md +++ b/docpages/example_programs/interactions_and_components/upload_parameter.md @@ -7,48 +7,4 @@ section, `event.command.resolved`. The file is uploaded to Discord's CDN so if you need it locally you should fetch the `.url` value, e.g. by using something like dpp::cluster::request(). -~~~~~~~~~~~~~~~~{.cpp} -#include - -int main() -{ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "show") { - - /* Get the file id from the parameter attachment. */ - dpp::snowflake file_id = std::get(event.get_parameter("file")); - - /* Get the attachment that the user inputted from the file id. */ - dpp::attachment att = event.command.get_resolved_attachment(file_id); - - /* Reply with the file as a URL. */ - event.reply(att.url); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create a new command. */ - dpp::slashcommand newcommand("show", "Show an uploaded file", bot.me.id); - - /* Add a parameter option. */ - newcommand.add_option(dpp::command_option(dpp::co_attachment, "file", "Select an image")); - - /* Register the command */ - bot.global_command_create(newcommand); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~~~~~~~ +\include{cpp} upload_parameter.cpp diff --git a/docpages/example_programs/interactions_and_components/user-only-messages.md b/docpages/example_programs/interactions_and_components/user-only-messages.md index 36b182cfe2..7576efc0d1 100644 --- a/docpages/example_programs/interactions_and_components/user-only-messages.md +++ b/docpages/example_programs/interactions_and_components/user-only-messages.md @@ -4,40 +4,7 @@ If you've used a discord bot, there's a chance that you've encountered a message Here's how you can do exactly that! -~~~~~~~~~~{.cpp} -#include - -int main() -{ - /* Create the bot */ - dpp::cluster bot("token"); - - bot.on_log(dpp::utility::cout_logger()); - - /* The event is fired when someone issues your commands */ - bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { - - /* Check which command they ran */ - if (event.command.get_command_name() == "hello") { - - /* Reply to the user, but only let them see the response. */ - event.reply(dpp::message("Hello! How are you today?").set_flags(dpp::m_ephemeral)); - } - }); - - bot.on_ready([&bot](const dpp::ready_t & event) { - if (dpp::run_once()) { - - /* Create and Register the command */ - bot.global_command_create(dpp::slashcommand("hello", "Hello there!", bot.me.id)); - } - }); - - bot.start(dpp::st_wait); - - return 0; -} -~~~~~~~~~~ +\include{cpp} ephemeral.cpp That's it! If everything went well, it should look like this: From ac0916124f89fa0eee105e6f8a5d95bc43910e8d Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Sep 2023 00:29:36 +0000 Subject: [PATCH 08/13] ci: test for docs examples --- .github/workflows/test-docs-examples.yml | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/test-docs-examples.yml diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml new file mode 100644 index 0000000000..057750539c --- /dev/null +++ b/.github/workflows/test-docs-examples.yml @@ -0,0 +1,45 @@ +name: Test compile documentation examples +on: + push: + branches: + - 'dev' + files: + - '**Doxyfile' + - '**docpages/example_code/**' + pull_request: + files: + - '**Doxyfile' + - '**docpages/example_code/**' + workflow_dispatch: + +jobs: + docs: + name: Test build examples + runs-on: ubuntu-22.04 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1 + with: + egress-policy: audit + + - name: Checkout D++ + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + with: + submodules: recursive + + - name: Install apt packages + run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt-get update && sudo apt-get install g++-12 libsodium-dev libopus-dev zlib1g-dev libmpg123-dev liboggz-dev + + - name: Generate CMake + run: mkdir build && cd build && cmake -DDPP_NO_VCPKG=ON -DAVX_TYPE=T_fallback -DDPP_CORO=ON -DCMAKE_BUILD_TYPE=Debug .. + env: + CXX: g++-12 + + - name: Build Project + run: cd build && make -j2 && sudo make install + + - name: Test compile examples + run: cd docpages/example_code && mkdir build && cd build && cmake .. && make -j2 + env: + CXX: g++-12 From 1a04bbbb090e44be07dd1bf44caab9791840a6f5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Sep 2023 01:03:39 +0000 Subject: [PATCH 09/13] ci: add missing dependencies to docs example test builder --- .github/workflows/test-docs-examples.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml index 057750539c..64a3044722 100644 --- a/.github/workflows/test-docs-examples.yml +++ b/.github/workflows/test-docs-examples.yml @@ -13,7 +13,7 @@ on: workflow_dispatch: jobs: - docs: + test_docs_examples: name: Test build examples runs-on: ubuntu-22.04 @@ -29,7 +29,7 @@ jobs: submodules: recursive - name: Install apt packages - run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt-get update && sudo apt-get install g++-12 libsodium-dev libopus-dev zlib1g-dev libmpg123-dev liboggz-dev + run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt-get update && sudo apt-get install -y g++-12 libsodium-dev libopus-dev zlib1g-dev libmpg123-dev liboggz-dev cmake libfmt-dev - name: Generate CMake run: mkdir build && cd build && cmake -DDPP_NO_VCPKG=ON -DAVX_TYPE=T_fallback -DDPP_CORO=ON -DCMAKE_BUILD_TYPE=Debug .. From d548f49f622fe143e4bd37be71c9222ba49b45c9 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Sep 2023 01:28:23 +0000 Subject: [PATCH 10/13] ci: fix up test-docs-examples --- .github/workflows/test-docs-examples.yml | 2 +- docpages/example_code/mp3.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml index 64a3044722..24d709c93a 100644 --- a/.github/workflows/test-docs-examples.yml +++ b/.github/workflows/test-docs-examples.yml @@ -37,7 +37,7 @@ jobs: CXX: g++-12 - name: Build Project - run: cd build && make -j2 && sudo make install + run: cd build && make -j32 && sudo make install - name: Test compile examples run: cd docpages/example_code && mkdir build && cd build && cmake .. && make -j2 diff --git a/docpages/example_code/mp3.cpp b/docpages/example_code/mp3.cpp index a085df531e..db15cb489d 100644 --- a/docpages/example_code/mp3.cpp +++ b/docpages/example_code/mp3.cpp @@ -51,14 +51,14 @@ int main(int argc, char const *argv[]) counter += buffer_size; totalBytes += done; } - delete buffer; + delete[] buffer; mpg123_close(mh); mpg123_delete(mh); /* Setup the bot */ dpp::cluster bot("token"); - bot.on_log(dpp::utility::cout_logger()); + bot.on_log(dpp::utility::cout_logger()); /* The event is fired when someone issues your commands */ bot.on_slashcommand([&bot, &pcmdata](const dpp::slashcommand_t& event) { From f018e64c3f0e522cb6b85ab0f93741f50388cae8 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Sep 2023 01:33:12 +0000 Subject: [PATCH 11/13] ci: reduce build concurrency back to 2 [skip ci] --- .github/workflows/test-docs-examples.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml index 24d709c93a..64a3044722 100644 --- a/.github/workflows/test-docs-examples.yml +++ b/.github/workflows/test-docs-examples.yml @@ -37,7 +37,7 @@ jobs: CXX: g++-12 - name: Build Project - run: cd build && make -j32 && sudo make install + run: cd build && make -j2 && sudo make install - name: Test compile examples run: cd docpages/example_code && mkdir build && cd build && cmake .. && make -j2 From b868d4bb1dbcc36ddef5a436235da818a31293f3 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Sep 2023 01:44:05 +0000 Subject: [PATCH 12/13] -Wextra -Wpedantic -Wno-unused-parameter --- docpages/example_code/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docpages/example_code/CMakeLists.txt b/docpages/example_code/CMakeLists.txt index a5d1309729..2f127da942 100644 --- a/docpages/example_code/CMakeLists.txt +++ b/docpages/example_code/CMakeLists.txt @@ -33,7 +33,7 @@ project(documentation_tests) string(ASCII 27 Esc) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Werror") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Wextra -Wpedantic -Werror -Wno-unused-parameter") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") file(GLOB example_list ./*.cpp) From f7aefe7000ed2348ef2a72e42daa4d918d7667bf Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Sep 2023 02:25:27 +0000 Subject: [PATCH 13/13] docs: fix missing example links [skip ci] --- .../the_basics/editing-channels-and-messages.md | 2 +- docpages/example_programs/using_coroutines/awaiting_events.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docpages/example_programs/the_basics/editing-channels-and-messages.md b/docpages/example_programs/the_basics/editing-channels-and-messages.md index e36258d358..a94ab7ccd8 100644 --- a/docpages/example_programs/the_basics/editing-channels-and-messages.md +++ b/docpages/example_programs/the_basics/editing-channels-and-messages.md @@ -4,7 +4,7 @@ Sometimes we need to update an object, such as message or channel. At first, it \note This example uses callback functions. To see more information about them, visit \ref callback-functions. -\example{cpp} editing_messages.cpp +\include{cpp} editing_messages.cpp Before editing: diff --git a/docpages/example_programs/using_coroutines/awaiting_events.md b/docpages/example_programs/using_coroutines/awaiting_events.md index 423d840f84..45b7a4dcfc 100644 --- a/docpages/example_programs/using_coroutines/awaiting_events.md +++ b/docpages/example_programs/using_coroutines/awaiting_events.md @@ -6,7 +6,7 @@ D++ makes it possible to await events: simple use `co_await` on any of the event \note When the event router resumes your coroutine, it will give you __a reference to the event object__. This will likely mean it will be destroyed after your next co_await, make sure to save it in a local variable if you need it for longer. -\example{cpp} coro_awaiting_events.cpp +\include{cpp} coro_awaiting_events.cpp Note that there is a problem with that! If the user never clicks your button, or if the message gets deleted, your coroutine will be stuck waiting... And waiting... Forever until your bot shuts down, occupying a space in memory. This is where the \ref expiring-buttons "next example" comes into play as a solution, with a button that expires with time.