스킵 구현

This commit is contained in:
2023-12-20 19:45:25 +09:00
parent 6503fd167b
commit 8a987320e0
775 changed files with 162601 additions and 135 deletions

View File

@@ -0,0 +1,10 @@
\page interactions-and-components Interactions And Components
Interactions are a unified way provided by Discord to handle \ref slashcommands "slash commands" and \ref components-menu "component interactions", such as \ref components "clicking a button". Slash commands are much better than message commands as they are not rate limited as much, meaning your bot can handle a lot more at once. They also contain a lot of data about a command, for example by making a command take a user as a parameter, \ref resolved-objects "the entire user object will be contained in the interaction" so you do not have to fetch it yourself, avoiding even more rate limits!
* \subpage slashcommands-menu
* \subpage user-only-messages
* \subpage resolved-objects
* \subpage components-menu
* \subpage modal-dialog-interactions
* \subpage context-menu

View File

@@ -0,0 +1,8 @@
\page components-menu Message Components
Components are anything that can be attached to a message or a \ref modal-dialog-interactions "modal" and interacted with, for example buttons and select menus. Due to being \ref interactions-and-components "interactions", they benefit from a low rate limit and are much more efficient than the old ways of using reactions for this purpose.
* \subpage components
* \subpage components2
* \subpage components3
* \subpage default_select_value

View File

@@ -0,0 +1,9 @@
\page components Using Button Components
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.
\include{cpp} components.cpp
When the feature is functioning, the code below will produce buttons on the reply message like in the image below:
\image html button.png

View File

@@ -0,0 +1,9 @@
\page components2 Advanced Button Components
This example demonstrates adding multiple buttons, receiving button clicks, and sending response messages.
\include{cpp} components2.cpp
This code will send a different message for correct and incorrect answers.
\image html button_2.png

View File

@@ -0,0 +1,17 @@
\page components3 Using Select Menu Components
This tutorial will cover creating two types of select menus:
- A generic select menu with just text
- An auto-populated role select menu.
This first example demonstrates creating a select menu, receiving select menu clicks, and sending a response message.
\include{cpp} components3.cpp
This second example demonstrates:
- Creating a role select menu that is auto-populated by Discord
- Allowing users to select two options!
\note This type of select menu, along with other types (these types being: user, role, mentionable, and channel), always auto-fill. You never need to define the data in these types of select menus. All select menu types allow you to select multiple options.
\include{cpp} components3_rolemenu.cpp

View File

@@ -0,0 +1,13 @@
\page default_select_value Setting Default Values on Select Menus.
This tutorial will cover how to set the default value for a select menu (that isn't a text select menu)!
\note **This only works for the following types of select menus: user, role, mentionable, and channel.** The default type of a select menu (as shown in \ref components3 "this page") does not work for this, as that supports a "placeholder".
\include{cpp} default_select_value.cpp
If all went well, you should have something like this!
\image html default_select_value.png
\image html default_select_value_2.png

View File

@@ -0,0 +1,15 @@
\page context-menu Context Menus
Context menus are application commands that appear on the context menu (right click or tap) of users or messages to perform context-specific actions. They can be created using dpp::slashcommand. Once you create a context menu, try right-clicking either a user or message to see it in your server!
\image html context_menu_user_command.png
\note This example sets the command as the type dpp::ctxm_user which can only be used by right clicking on a user. To make it appear on a message, you'll want to switch the type to dpp::ctxm_message and listen for the `on_message_context_menu` (dpp::message_context_menu_t) event.
The following example shows how to create and handle **user context menus** for message context menus, read the notice above.
\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.
\image html context_menu_user_command_showcase.png

View File

@@ -0,0 +1,12 @@
\page modal-dialog-interactions Modals
Modal dialog interactions are a new Discord API feature that allow you to have pop-up windows which prompt the user to input information. Once the user has filled in this information, your program will receive an `on_form_submit` event which will contain the data which was input. You must use a slash command interaction response to submit your modal form data to Discord, via the `on_slashcommand` event. From here calling the `dialog` method of the `interaction_create_t` event object will trigger the dialog to appear.
Each dialog box may have up to five rows of input fields. The example below demonstrates a simple setup with just one text input:
\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:
\image html modal_dialog.png

View File

@@ -0,0 +1,9 @@
\page resolved-objects Using Resolved Objects
If your slash command accepts options like user, channel, or role you can get their value, as specified by the user in the command, from parameters. Though parameter gives you only the snowflake id of the passed value.
If you need object of that snowflake, you can get that from the resolved set using its snowflake id.
Below is an example showing how to get a member, passed in command options, using resolved set.
\include{cpp} resolved_objects.cpp

View File

@@ -0,0 +1,12 @@
\page slashcommands-menu Slash commands
Here, you will find examples on how to use slash commands, subcommands, and different types or parameters.
* \subpage slashcommands "Using Slash Commands"
* \subpage clearing_slashcommands
* \subpage subcommands "Slash command sub-commands"
* \subpage application-command-autocomplete "Slash command auto completion"
* \subpage discord-application-command-file-upload "Using file parameters in slash commands"
* \subpage commandhandler "Unified message/slash command handler"
\note Keep in mind, a slash command when sent to your bot is <b>NOT SENT AS A MESSAGE</b>! It is not a string but an interaction structure with its own data, and \ref resolved-objects "command parameters are objects".

View File

@@ -0,0 +1,5 @@
\page application-command-autocomplete Slash Command Autocompletion
Discord now supports sending auto completion lists for slash command choices. To use this feature you can use code such as the example below:
\include{cpp} autocomplete.cpp

View File

@@ -0,0 +1,7 @@
\page clearing_slashcommands Clearing Registered Commands
After a while of creating commands, you may start to wonder "hm, how can I clear these?". Well, this tutorial covers it! All you have to do is simply call dpp::cluster::global_bulk_command_delete or dpp::cluster::guild_bulk_command_delete.
\note Clearing **global commands** will only clear commands that were **made globally**, same goes for the opposite way round. The example will demonstrate using both functions.
\include{cpp} clearing_slashcommands.cpp

View File

@@ -0,0 +1,15 @@
\page commandhandler Using a Command Handler Object
If you have many commands in your bot, and want to handle commands from multiple sources, you should consider instantiating a dpp::commandhandler object. This object can be used to automatically route
commands and their parameters to functions in your program. A simple example of using this object to route commands is shown below, and will
route both the /ping (global slash command) and .ping (prefixed channel message command) to a lambda where a reply can be generated.
\note This example automatically hooks the dpp::cluster::on_message_create and dpp::cluster::on_slashcommand events. This can be overridden if needed to allow you to still make use of these functions for your own code, if you need to do this please see the constructor documentation for dpp::commandhandler.
Note that because the dpp::commandhandler::add_command method accepts a `std::function` as the command handler, you may point a command handler
at a simple lambda (as shown in this example), a function pointer, or an instantiated class method of an object. This is extremely flexible
and allows you to decide how and where commands should be routed, either to an object oriented system or to a lambda based system.
\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.
\include{cpp} commandhandler.cpp

View File

@@ -0,0 +1,29 @@
\page slashcommands Using Slash Commands and Interactions
Slash commands and interactions are a newer feature of Discord which allow a bot's commands to be registered centrally within the system and for users to easily explore and get help with available commands through the client itself.
To add a slash command you should use the dpp::cluster::global_command_create method for global commands (available to all guilds) or dpp::cluster::guild_command_create to create a local command (available only to one guild). If you want to add many commands, it is advised to use the dpp::cluster::global_bulk_command_create method for global commands or the dpp::cluster::guild_bulk_command_create method for local commands.
\note dpp::cluster::global_bulk_command_create and dpp::cluster::guild_bulk_command_create will delete any previous commands that were registered. For example, if you call dpp::cluster::global_bulk_command_create twice with two different sets then the first set of commands will be created, then when the second set is called, the first set will be deleted, leaving only the second set.
When a user issues these commands the reply will arrive via the `on_slashcommand` event which you can hook into, and take action when you see your commands. It is possible to reply to an interaction by using either the dpp::interaction_create_t::reply method, or by manually instantiating an object of type dpp::interaction_response and attaching a dpp::message object to it.
dpp::interaction_create_t::reply has two overloaded versions of the method, one of which accepts simple `std::string` replies, for basic text-only messages (if your message is 'ephemeral' you must use this) and one which accepts a dpp::message for more advanced replies. Please note that at present, Discord only supports a small subset of message and embed features within an interaction response object.
This first example goes over creating a single command globally.
\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.
\include{cpp} slashcommands2.cpp
This third example goes over creating four commands globally, using the bulk create method.
\include{cpp} slashcommands3.cpp
This fourth example goes over creating four commands but only for a guild.
\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.

View File

@@ -0,0 +1,5 @@
\page subcommands Using Sub-Commands in Slash Commands
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.
\include{cpp} subcommands.cpp

View File

@@ -0,0 +1,10 @@
\page discord-application-command-file-upload Using File Parameters for Application Commands (Slash Commands)
The program below demonstrates how to use the 'file' type parameter to an application command (slash command).
You must first get the `file_id` via `std::get`, and then you can find the attachment details in the 'resolved'
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().
\include{cpp} upload_parameter.cpp

View File

@@ -0,0 +1,11 @@
\page user-only-messages Ephemeral Replies ('Only You Can See This' Replies)
If you've used a Discord bot, there's a chance that you've encountered a message from one that said "Only you can see this" after you interacted with it (or executed a command). These messages are pretty helpful and can be used in many instances where you'd only like the user that's interacting to see what's going on.
Here's how you can do exactly that!
\include{cpp} ephemeral.cpp
That's it! If everything went well, it should look like this:
\image html user_only_messages_image.png

View File

@@ -0,0 +1,14 @@
\page misc Miscellaneous Examples
This section lists examples that do not fit neatly into any of the categories above.
* \subpage making_a_http_request
* \subpage spdlog
* \subpage editing-channels-and-messages
* \subpage making_threads
* \subpage caching-messages
* \subpage collecting-reactions
* \subpage cpp-eval-command-discord
* \subpage checking-member-permissions
* \subpage setting_status
* \subpage using-emojis

View File

@@ -0,0 +1,9 @@
\page caching-messages Caching Messages
By default D++ does not cache messages. The example program below demonstrates how to instantiate a custom cache using dpp::cache which will allow you to cache messages and query the cache for messages by ID.
This can be adjusted to cache any type derived from dpp::managed including types you define yourself.
\note This example will cache and hold onto messages forever! In a real world situation this would be bad. If you do use this, 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.
\include{cpp} cache_messages.cpp

View File

@@ -0,0 +1,87 @@
\page checking-member-permissions Checking Permissions
Of course most people do just iterate over the roles of a member to check for a permission. But there's a helper method for that: dpp::guild::base_permissions gets a member's permission taking into account the server owner and role permissions.
For total member permissions including channel overwrites use either the dpp::channel::get_user_permissions or dpp::guild::permission_overwrites method. Both do the same under the hood.
They all return a dpp::permission class, which is a wrapper around a permission bitmask containing bits of the dpp::permissions enum.
Demonstration:
```cpp
dpp::channel* c = dpp::find_channel(some_channel_id);
if (c && c->get_user_permissions(member).can(dpp::p_send_messages)) {
//...
}
```
## Permissions in Interaction Events
### Default Command Permissions
Discord's intended way to manage permissions for commands is through default member permissions. You set them using dpp::slashcommand::set_default_permissions when creating or updating a command to set the default permissions a user must have to use it. However, server administrators can then overwrite these permissions by their own restrictions.
The corresponding code to create a command with default permissions would look something like this:
```cpp
dpp::slashcommand command("ban", "Ban a member", bot.me.id);
command.set_default_permissions(dpp::p_ban_members); // set permissions that are required by default here
command.add_option(dpp::command_option(dpp::co_user, "user", "The user to ban", true));
command.add_option(dpp::command_option(dpp::co_string, "reason", "The reason for banning", true));
bot.global_command_create(command);
```
### Checking Permissions on Your Own
If you want to check permissions on your own, the easiest way to check if a member has certain permissions in interaction events is by using the dpp::interaction::get_resolved_permission function. The resolved list contains associated structures for the command and does not use the cache or require any extra API calls. Note that the permissions in the resolved set are pre-calculated by Discord and taking into account channel overwrites, roles and admin privileges. So no need to loop through roles or stuff like that.
Let's imagine the following scenario:
You have a ban command and want to make sure the issuer has the ban permission.
```cpp
bot.on_interaction_create([](const dpp::interaction_create_t& event) {
dpp::permission perms = event.command.get_resolved_permission(event.command.usr.id);
if (!perms.can(dpp::p_ban_members)) {
event.reply("You don't have the required permissions to ban someone!");
return;
}
});
```
\note When using default permissions you don't necessarily need to check the issuing user for any permissions in the interaction event as Discord handles all that for you. But if you'd sleep better...
### From Parameters
The resolved set also contains the permissions of members from command parameters.
For example, let's say you want to prohibit people from banning server admins with your ban command.
Get the user ID from the parameters and pass it to the `get_resolved_permission` method:
```cpp
bot.on_interaction_create([](const dpp::interaction_create_t& event) {
dpp::snowflake user_id = std::get<dpp::snowflake>(event.get_parameter("user"));
dpp::permission perms = event.command.get_resolved_permission(user_id);
if (perms.has(dpp::p_administrator)) {
event.reply("You can't ban Admins!");
return;
}
});
```
### The Bot's Permissions
You also might want to check if the bot itself has the ban permission before processing the command further. You can access the bot's permissions in the dpp::interaction::app_permissions field.
```cpp
bot.on_interaction_create([](const dpp::interaction_create_t& event) {
if (!event.command.app_permissions.can(dpp::p_ban_members)) {
event.reply("The bot doesn't have the required permission to ban anyone!");
return;
}
});
```

View File

@@ -0,0 +1,8 @@
\page collecting-reactions Collecting Reactions
D++ comes with many useful helper classes, but amongst these is something called dpp::collector. Collector is a template which can be specialised to automatically collect objects of a predetermined type from events for a specific interval of time. Once this time period is up, or the class is otherwise signalled, a method is called with the complete set of collected objects.
In the example below, we will use it to collect all reactions on a message.
\include{cpp} collect_reactions.cpp

View File

@@ -0,0 +1,50 @@
\page cpp-eval-command-discord Making an eval Command in C++
## What is an eval command anyway?
Many times people will ask: "How do I make a command like 'eval' in C++?". For the uninitiated, an `eval` command is a command often found in interpreted languages such as JavaScript and Python, which allows the developer to pass in raw interpreter statements which are then executed within the context of the running program, without any sandboxing. Eval commands are plain **evil**, if not properly coded in.
Needless to say, this is very dangerous. If you are asking how to do this, and want to put this into your bot, we trust that you have a very good reason to do so and have considered alternatives before resorting to this. The code below is for educational purposes only and makes several assumptions:
1. This code will only operate on UNIX-like systems such as Linux (not **Darwin**).
2. It assumes you use GCC, and have `g++` installed on your server and in your `${PATH}`.
3. The program will attempt to write to the current directory.
4. No security checks will be done against the code, except for to check that it is being run by the bot's developer by snowflake id. It is entirely possible to send an `!eval exit(0);` and make the bot quit, for example, or delete files from the host operating system, if misused or misconfigured.
5. You're willing to wait a few seconds for compilation before your evaluated code runs. There isn't a way around this, as C++ is a compiled language.
To create this program you must create two files, `eval.h` and `eval.cpp`. The header file lists forward declarations of functions that you will be able to use directly within your `eval` code. As well as this the entirety of D++ will be available to the eval command via the local variable `bot`, and the entire `on_message_create` event variable via a local variable called `event`.
The evaluated code will run within its own thread, so can execute for as long as it needs (but use common sense, don't go spawning a tight `while` loop that runs forever, you'll lock a thread at 100% CPU that won't ever end!).
## Implementation details
This code operates by outputting your provided code to be evaluated into a simple boilerplate program which can be compiled to a shared object library (`.so`) file. This `.so` file is then compiled with `g++`, using the `-shared` and `-fPIC` flags. If the program can be successfully compiled, it is then loaded using `dlopen()`, and the symbol `so_exec()` searched for within it, and called. This `so_exec()` function will contain the body of the code given to the eval command. Once this has been called and it has returned, the `dlclose()` function is called to unload the library, and finally any temporary files (such as the `.so` file and its corresponding `.cpp` file) are cleaned up. Docker is definitely recommended if you code on Windows/macOS, because Docker desktop still uses a Linux VM, so your code can easily use the `.so` file and your code runs the same on your VPS (if it also uses Linux distro).
## Source code
\warning If you manage to get your system, network, or anything else harmed by use or misuse of this code, we are not responsible. Don't say we didn't warn you! Find another way to solve your problem!
### eval.h
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.
\include{cpp} eval.h
### eval.cpp
This is the main body of the example program.
\include{cpp} eval.cpp
## Compilation
To compile this program you must link against `libdl`. It is also critically important to include the `-rdynamic` flag. For example:
```
g++ -std=c++17 -rdynamic -oeval eval.cpp -ldpp -ldl
```
## Example usage
\image html eval_example.png

View File

@@ -0,0 +1,5 @@
\page making_a_http_request Making Arbitrary HTTP Requests Using D++
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:
\include{cpp} http_request.cpp

View File

@@ -0,0 +1,35 @@
\page making_threads Creating and Interacting with Threads
A new feature added to Discord recently is `Threads`, these allow you to break off a message into a different "channel", without creating a whole new channel. There are also other types of "thread channels", one example being a `forums channel`. This type of channel only contains threads, meaning you can't send messages in it so if you want to make one of them, be careful about trying to send a message in it!
In this tutorial, we'll be going through:
- How to create a thread.
- How to loop through all the active threads in a server (and sending a message in one).
- How to lock a thread (editing threads).
First, let's go through creating a thread.
\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 looping through all the threads in a server. For this demonstration, we'll be picking the first thread we find in the list and sending a message in it.
\include{cpp} making_threads2.cpp
After that, you'll be able to see your bot send a message in your thread!
\image html creating_thread_2.png
Those of you who are familar with sending messages in regular channels may have also noticed that sending messages to threads is the same as sending a general message. This is because threads are basically channels with a couple more features!
Now, we're going to cover how to lock a thread! With this, you'll also learn how to edit threads in general, meaning you can go forward and learn how to change even more stuff about threads, as much as your heart desires!
\include{cpp} making_threads3.cpp
Once you've ran that, you'll see that you were successfully able to lock a thread!
\image html creating_thread_3.png

View File

@@ -0,0 +1,28 @@
\page setting_status Setting the Bot's Status
A bot status is pretty cool, and it'd be cooler if you knew how to do it! This tutorial will cover how to set the bot status to say `Playing games!`, as well as covering how to set the status to the amount of guilds every two minutes.
\note dpp::get_guild_cache requires the bot to have the guild cache enabled, if your bot has this disabled then you can't use that. Instead, you should look to use dpp::cluster::current_application_get and get the `approximate_guild_count` from dpp::application in the callback.
First, we'll cover setting the bot status to `Playing games!`.
\include{cpp} setting_status1.cpp
If all went well, your bot should now be online and say this on members list!
\image html botonlinestatus.png
If you want to make your bot show as Do Not Disturb, then you could change dpp::ps_online to dpp::ps_dnd.
You can also play around with dpp::at_game, changing it to something like dpp::at_custom or dpp::at_listening!
Now, let's cover setting the bot status to say `Playing with x guilds!` every two minutes.
\include{cpp} setting_status2.cpp
If you followed that well, your bot should now say this on members list!
\image html botonlinestatus2.png
If we then add our bot to another server and wait a bit, we'll see it updates like so:
\image html botonlinestatus3.png

View File

@@ -0,0 +1,62 @@
\page spdlog Integrating with spdlog
If you want to make your bot use spdlog, like aegis does, you can attach it to the `on_log` event. You can do this as follows:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~cpp
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <iomanip>
#include <dpp/dpp.h>
#include <fmt/format.h>
int main(int argc, char const *argv[]) {
dpp::cluster bot("token");
const std::string log_name = "mybot.log";
/* Set up spdlog logger */
std::shared_ptr<spdlog::logger> log;
spdlog::init_thread_pool(8192, 2);
std::vector<spdlog::sink_ptr> sinks;
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >();
auto rotating = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_name, 1024 * 1024 * 5, 10);
sinks.push_back(stdout_sink);
sinks.push_back(rotating);
log = std::make_shared<spdlog::async_logger>("logs", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
spdlog::register_logger(log);
log->set_pattern("%^%Y-%m-%d %H:%M:%S.%e [%L] [th#%t]%$ : %v");
log->set_level(spdlog::level::level_enum::debug);
/* Integrate spdlog logger to D++ log events */
bot.on_log([&bot, &log](const dpp::log_t & event) {
switch (event.severity) {
case dpp::ll_trace:
log->trace("{}", event.message);
break;
case dpp::ll_debug:
log->debug("{}", event.message);
break;
case dpp::ll_info:
log->info("{}", event.message);
break;
case dpp::ll_warning:
log->warn("{}", event.message);
break;
case dpp::ll_error:
log->error("{}", event.message);
break;
case dpp::ll_critical:
default:
log->critical("{}", event.message);
break;
}
});
/* Add the rest of your events */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -0,0 +1,29 @@
\page using-emojis Using Emojis
Need your bot to use an emoji? Then you've come to the right place! Here are three examples of using emojis.
\note If your bot isn't in the guild you want to use the custom emoji from, it won't work, giving you dpp::err_unknown_emoji.
First - Sending emojis. You have to use its mention, which depends on the type. If it's a default emoji, you use the corresponding character. So, for example, if you wanted to send a nerd emoji, you would use the nerd unicode character. Now, custom emoji. There are two types: static and animated. Their mentions are `<:[name]:[id]>` and `<a:[name]:[id]>`, where `[name]` means the emoji name and `[id]` is for its ID. When you send such mention, it automatically gets converted into your emoji. Here's an example of sending emojis:
\include{cpp} using_emojis1.cpp
Now, our bot will send our epic emojis!
\image html using_emojis1.png
Second - Reacting to messages. Sometimes there's something so interesting in the chat that we want to react to it. While we see the emoji we react with, for bots, it's some plain text. There are different formats for different kinds of emoji when reacting too. For unicode, it's simply its character, like when sending. For custom ones it's either `[name]:[id]` (if static) or `a:[name]:[id]` (if animated). Let's show our bot's honest reactions!
\include{cpp} using_emojis2.cpp
Yay, our bot has emotions now!
\image html using_emojis2.png
Finally, select menus. These guys are covered \ref components3 "here". They require emoji components (name, ID, animated state) to come separately. If the emoji you're using isn't animated, you don't have to specify that. If your emoji is unicode, it doesn't even have an ID, so you only put the character, since both animated state and ID are defaulted to none (false/0).
\include{cpp} using_emojis3.cpp
Yay, our context menu is now interesting!
\image html using_emojis3.png

View File

@@ -0,0 +1,9 @@
\page music-and-audio Music and Audio
If you want to make noise, or capture noise, you're in the right place. You'll find examples here for creating basic music bots, or recording voice, amongst other things.
* \subpage soundboard
* \subpage oggopus
* \subpage stream-mp3-discord-bot
* \subpage record-user
* \subpage joinvc

View File

@@ -0,0 +1,7 @@
\page joinvc Join or Switch to the Voice Channel of the User Issuing a Command
When a user issues a command you may want to join their voice channel, e.g. in a music bot. If you are already on the same voice channel, the bot should do nothing (but be ready to instantly play audio) and if the user is on a different voice channel, the bot should switch to it. If the user is on no voice channel at all, this should be considered an error. This example shows how to do this.
\note Please be aware this example sends no audio, but indicates clearly in the comments where and how you should do so.
\include{cpp} join_voice.cpp

View File

@@ -0,0 +1,13 @@
\page stream-mp3-discord-bot Streaming MP3 Files
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:
\include{cpp} mp3.cpp
To compile this program you must remember to specify `libmpg123` alongside `libdpp` in the build command, for example:
```bash
g++ -std=c++17 -o musictest musictest.cpp -lmpg123 -ldpp
```
\include{doc} install_prebuilt_footer.dox

View File

@@ -0,0 +1,25 @@
\page oggopus Streaming Ogg Opus file
This example shows how to stream an Ogg Opus file to a voice channel. This example requires some additional dependencies, namely `libogg` and `opusfile`.
\include{cpp} oggopus.cpp
You can compile this example using the following command
```bash
c++ /path/to/source.cc -ldpp -lopus -lopusfile -logg -I/usr/include/opus
```
## Using liboggz
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/).
\include{cpp} oggopus2.cpp
You can compile this example using the following command:
```bash
c++ /path/to/source.cc -ldpp -loggz
```

View File

@@ -0,0 +1,7 @@
\page record-user Record Yourself in a VC
DPP supports receiving audio. This example shows how to use it to record some user in a VC.
\note Voice receiving by bots is not officially supported by the Discord API. We cannot guarantee that this feature will work in the future.
\include{cpp} record_user.cpp

View File

@@ -0,0 +1,5 @@
\page soundboard Creating a Sound Board
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 .
\include{cpp} soundboard.cpp

View File

@@ -0,0 +1,12 @@
\page the-basics The Basics
These example programs are great to get started with simple things in the D++ library, ideal for beginners to the language or to the Discord API.
* \subpage firstbot
* \subpage embed-message
* \subpage private-messaging
* \subpage attach-file
* \subpage webhooks
* \subpage callback-functions
* \subpage using-cache
* \subpage detecting-messages

View File

@@ -0,0 +1,21 @@
\page attach-file Attaching a File to a Message
To attach a local file to an message, you can use dpp::utility::read_file. It's a helper function from D++ that allows you to read the file's content and sent it to discord.
An example of this:
\include{cpp} attachments1.cpp
Attachments via URL aren't possible. But there's a workaround for this! You can download the file and then attach it to the message.
Amazingly, D++ also has a function for this! You can use dpp::cluster::request to make HTTP requests, allowing you to go ahead and pull the content from a URL.
The following example program shows how to request a file and attach it to a message:
\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.
\include{cpp} attachments3.cpp

View File

@@ -0,0 +1,11 @@
\page detecting-messages Listening to messages
Sometimes, you may want to listen out for a message, rather than a command. This could be used for many cases like a spam filter, a bot that would respond to movie quotes with gifs, or a chat bot! However, in this page, we'll be using this to create a moderation filter (detect bad words).
\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.
\include{cpp} detecting_messages.cpp
If all went well, you should have something like this!
\image html badwordfilter.png

View File

@@ -0,0 +1,15 @@
\page editing-channels-and-messages Editing Channels and Messages
Sometimes we need to update an object, such as message or channel. At first, it might seem confusing, but it's actually really simple! You just need to use an object with identical properties you don't need to update. NOTE: your bot can't edit messages sent by others.
\note This example uses callback functions. To see more information about them, visit \ref callback-functions.
\include{cpp} editing_messages.cpp
Before editing:
\image html stuff_edit1.png
After editing:
\image html stuff_edit2.png

View File

@@ -0,0 +1,9 @@
\page embed-message Sending Embeds
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!
\include{cpp} embeds.cpp
The code will send the following message.
\image html embed.png

View File

@@ -0,0 +1,124 @@
\page firstbot Creating Your First Bot
In this example we will create a C++ version of the [discord.js](https://discord.js.org/#/) example program.
The two programs can be seen side by side below:
<table>
<tr>
<th>D++</th>
<th>Discord.js</td>
</tr>
<tr>
<td>
\include{cpp} firstbot.cpp
</td>
<td>
~~~~~~~~~~~~~~~js
let Discord = require('discord.js');
let BOT_TOKEN = 'add your token here';
let bot = new Discord.Client({ intents: [] });
bot.on('interactionCreate', (interaction) => {
if (interaction.isCommand() && interaction.commandName === 'ping') {
interaction.reply({content: 'Pong!'});
}
});
bot.once('ready', async () => {
await client.commands.create({
name: 'ping',
description: "Ping pong!"
});
});
bot.login(BOT_TOKEN);
~~~~~~~~~~~~~~~
</td>
</tr>
</table>
Let's break this program down step by step:
## 1. Start with an empty C++ program
Make sure to include the header file for the D++ library with the instruction `#include <dpp/dpp.h>`!
\include{cpp} firstbot1.cpp
## 2. Create an instance of dpp::cluster
To make use of the library you must create a dpp::cluster object. This object is the main object in your program like the `Discord.Client` object in Discord.js.
You can instantiate this class as shown below. Remember to put your bot token in the constant!
\include{cpp} firstbot2.cpp
## 3. Attach to an event
To have a bot that does something, you should attach to some events. Let's start by attaching to the `on_ready` event (dpp::cluster::on_ready) which will notify your program when the bot is connected. In this event, we will register a slash 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).
\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:
\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!`:
\include{cpp} firstbot5.cpp
Let's break down the code in the `on_slashcommand` event so that we can discuss what it is doing:
~~~~~~~~~~~~~~~~~~~~~~~cpp
bot.on_slashcommand([](const dpp::slashcommand_t& event) {
if (event.command.get_command_name() == "ping") {
event.reply("Pong!");
}
});
~~~~~~~~~~~~~~~~~~~~~~~
This code is simply comparing the command name `event.command.get_command_name()` (dpp::interaction::get_command_name) against the value in a constant string value `"ping"`. If they match, then the `event.reply` method is called.
The `event.reply` function (dpp::slashcommand_t::reply) replies to a slash command with a message. There are many ways to call this function to send embed messages, upload files, and more, but for this simple demonstration we will just send some message text.
## 6. Add code to start the bot!
To make the bot start, we must call the dpp::cluster::start method, e.g. in our program by using `bot.start(dpp::st_wait)`.
We also add a line to tell the library to output all its log information to the console, `bot.on_log(dpp::utility::cout_logger());` - if you wanted to do something more advanced, you can replace this parameter with a lambda just like all other events.
The parameter which we set to false indicates if the function should return once all shards are created. Passing dpp::st_wait here tells the program you do not need to do anything once `bot.start` is called.
\include{cpp} firstbot6.cpp
## 7. Compile and run your bot
Compile your bot using `g++ -std=c++17 -o bot bot.cpp -ldpp` (if your .cpp file is called `bot.cpp`) and run it with `./bot`.
## 8. Inviting your bot to your server
When you invite your bot, you must use the `applications.commands` and `bots` scopes to ensure your bot can create guild slash commands. For example:
```url
https://discord.com/oauth2/authorize?client_id=YOUR-BOTS-ID-HERE&scope=bot+applications.commands&permissions=BOT-PERMISSIONS-HERE
```
Replace `YOUR-BOTS-ID-HERE` with your bot's user ID, and `BOT-PERMISSIONS-HERE` with the permissions your bot requires.
**Congratulations** - you now have a working bot using the D++ library!

View File

@@ -0,0 +1,12 @@
\page private-messaging Sending private messages
Sometimes it's simply not enough to ping someone in a server with a message, and we get that. That's why you can private message people! This tutorial will cover how to make a command that will either message the author of the command or message a specified user!
\note This tutorial makes use of callbacks. For more information about that, visit \ref callback-functions "Using Callback Functions".
\include{cpp} private_messaging.cpp
That's it! Now, you should have something like this:
\image html privatemessageexample.png
\image html privatemessageexample2.png

View File

@@ -0,0 +1,18 @@
\page using-cache Using Cache
Sometimes you may need information that is not directly available in the event callback object.
To handle this DPP maintains a cache of commonly used data for you.
@note As of August 30th, 2022, Discord made Guild Members a privileged intent. You must have GUILD_MEMBERS intent enabled for your app from discord developers portal to be able to look for members in cache.
Below is an example showing how to get a user from the cache
\include{cpp} using_cache.cpp
DPP caches more than just users, which you can get using the below-mentioned functions:
- `dpp::find_role()`
- `dpp::find_channel()`
- `dpp::find_emoji()`
- `dpp::find_guild()`
- `dpp::find_guild_member()`

View File

@@ -0,0 +1,9 @@
\page callback-functions Using Callback Functions
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:
\include{cpp} callbacks.cpp
This is the result:
\image html callback_functions.png

View File

@@ -0,0 +1,9 @@
\page webhooks Webhooks
Webhooks are a simple way to post messages from other apps and websites into Discord. They allow getting automated messages and data updates sent to a text channel in your server. [Read more](https://support.discord.com/hc/en-us/articles/228383668) in this article about Webhooks.
The following code shows how to send messages in a channel using a webhook.
\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.

View File

@@ -0,0 +1,15 @@
\page using-coroutines Using Coroutines
\include{doc} coro_warn.dox
One of the most anticipated features of C++20 is the addition of coroutines: in short, they are functions that can be paused while waiting for data and resumed when that data is ready. They make asynchronous programs much easier to write, but they do come with additional dangers and subtleties.
* \subpage coro-introduction
* \subpage coro-simple-commands
* \subpage awaiting-events
* \subpage expiring-buttons
Coroutines are currently disabled by default; to use them you will need to build D++ \ref install-from-source "from source" and use the option `-DDPP_CORO=on` in your CMake command.
Your application also needs to enable C++20 and define DPP_CORO, by using:
- `-std=c++20 -DDPP_CORO` in your build command if building manually, or
- if using CMake, add `target_compile_definitions(my_program PUBLIC DPP_CORO)` and `target_compile_features(my_program PUBLIC cxx_std_20)`.

View File

@@ -0,0 +1,13 @@
\page awaiting-events Waiting for events
\include{doc} coro_warn.dox
D++ makes it possible to await events: simple use `co_await` on any of the event routers, such as \ref dpp::cluster::on_message_create "on_message_create", and your coroutine will be suspended until the next event fired by this event router. You can also `co_await` the return of an event router's \ref dpp::event_router_t::when "when()" method while passing it a predicate function object, it will only resume your coroutine when the predicate returns true. Be aware that your coroutine is attached to the event router only when you call `co_await` and not before, and will be detached as it is resumed.
\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.
\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.
\image html waiting_coroutine.jpg

View File

@@ -0,0 +1,20 @@
\page coro-introduction Introduction to coroutines
Introduced in C++20, coroutines are the solution to the impracticality of callbacks. In short, a coroutine is a function that can be paused and resumed later. They are an extremely powerful alternative to callbacks for asynchronous APIs in particular, as the function can be paused when waiting for an API response, and resumed when it is received.
Let's revisit \ref attach-file "attaching a downloaded file", but this time with a coroutine:
\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.
In order to be a coroutine, a function has to return a special type with special functions; D++ offers dpp::job, dpp::task, and dpp::coroutine, which are designed to work seamlessly with asynchronous calls through dpp::async, which all the functions starting with `co_` such as dpp::cluster::co_message_create return. Event routers can have a dpp::task coroutine attached to them, as this object allows to create coroutines that can execute on their own, asynchronously. More on that and the difference between it and the other two types later. To turn a function into a coroutine, simply make it return dpp::task<void> as seen in the example at line 10, then use `co_await` on awaitable types or `co_return`. The moment the execution encounters one of these two keywords, the function is transformed into a coroutine. Coroutines that use dpp::task<void> can be used for event handlers, they can be attached to an event router just the same way as regular event handlers.
When using a `co_*` function such as `co_message_create`, the request is sent immediately and the returned dpp::async can be `co_await`-ed, at which point the coroutine suspends (pauses) and returns back to its caller; in other words, the program is free to go and do other things while the data is being retrieved and D++ will resume your coroutine when it has the data you need, which will be returned from the `co_await` expression.
\warning As a rule of thumb when making coroutines, **always prefer taking parameters by value and avoid lambda capture**! See below for an explanation.
You may hear that coroutines are "writing async code as if it was sync", while this is sort of correct, it may limit your understanding and especially the dangers of coroutines. I find **they are best thought of as a shortcut for a state machine**, if you've ever written one, you know what this means. Think of the lambda as *its constructor*, in which captures are variable parameters. Think of the parameters passed to your lambda as data members in your state machine. When you `co_await` something, the state machine's function exits, the program goes back to the caller, at this point the calling function may return. References are kept as references in the state machine, which means by the time the state machine is resumed, the reference may be dangling: \ref lambdas-and-locals "this is not good"!
Another way to think of them is just like callbacks but keeping the current scope intact. In fact this is exactly what it is, the co_* functions call the normal API calls, with a callback that resumes the coroutine, *in the callback thread*. This means you cannot rely on thread_local variables and need to keep in mind concurrency issues with global states, as your coroutine will be resumed in another thread than the one it started on.

View File

@@ -0,0 +1,21 @@
\page coro-simple-commands Making simple commands
\include{doc} coro_warn.dox
### Several steps in one
\note The next example assumes you are already familiar with how to use \ref firstbot "slash commands", \ref slashcommands "parameters", and \ref discord-application-command-file-upload "sending files through a command".
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 :
\include{cpp} coro_simple_commands1.cpp
### I heard you liked tasks
\note This next example is fairly advanced and makes use of many of both C++ and D++'s advanced features.
Earlier we mentioned two other types of coroutines provided by dpp: dpp::coroutine and dpp::task. They both take their return type as a template parameter, which may be void. Both dpp::job and dpp::task start on the constructor for asynchronous execution, however only the latter can be `co_await`-ed, this allows you to retrieve its return value. If a dpp::task is destroyed before it ends, it is cancelled and will stop when it is resumed from the next `co_await`. dpp::coroutine also has a return value and can be `co_await`-ed, however it only starts when `co_await`-ing, meaning it is executed synchronously.
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:
\include{cpp} coro_simple_commands2.cpp

View File

@@ -0,0 +1,11 @@
\page expiring-buttons Making expiring buttons with when_any
\include{doc} coro_warn.dox
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.
\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.
\note when_any will try to construct awaitable objects from the parameter you pass it, which it will own. In practice this means you can only pass it <a href="https://www.learncpp.com/cpp-tutorial/value-categories-lvalues-and-rvalues/">temporary objects (rvalues)</a> as most of the coroutine-related objects in D++ are move-only.