D++ (DPP)
C++ Discord API Bot Library
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Pages
Making expiring buttons with when_any

Warning
D++ Coroutines are currently only supported by D++ on g++ 13, clang/LLVM 14, and MSVC 19.37 or above. Additionally, your program has to support C++20.

In the last example we've explored how to 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 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 result object that allows to retrieve which awaitable completed as well as its result, in a similar way as std::variant.

#include <dpp/dpp.h>
int main() {
dpp::cluster bot{"token"};
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
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.owner->on_button_click.when([&id](const dpp::button_click_t &b) { // Button clicked
return b.custom_id == id;
}),
event.owner->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<struct register_bot_commands>()) {
dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id};
bot.global_command_create(command);
}
});
bot.start(dpp::st_wait);
return 0;
}

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 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 temporary objects (rvalues) as most of the coroutine-related objects in D++ are move-only.
dpp::cluster::me
dpp::user me
The details of the bot user. This is assumed to be identical across all shards in the cluster....
Definition: cluster.h:274
dpp::button_click_t::custom_id
std::string custom_id
button custom id
Definition: dispatcher.h:797
dpp::managed::id
snowflake id
Unique ID of object set by Discord. This value contains a timestamp, worker ID, internal server ID,...
Definition: managed.h:79
dpp::interaction_create_t::co_reply
dpp::async< dpp::confirmation_callback_t > co_reply() const
Acknowledge interaction without displaying a message to the user, for use with button and select menu...
dpp::slashcommand_t
User has issued a slash command.
Definition: dispatcher.h:779
dpp::st_wait
@ st_wait
Wait forever on a condition variable. The cluster will spawn threads for each shard and start() will ...
Definition: cluster.h:91
dpp::task
A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for...
Definition: coro/coro.h:185
dpp::component::set_label
component & set_label(std::string_view label)
Set the label of the component, e.g. button text. For action rows, this field is ignored....
dpp::message
Represents messages sent and received on Discord.
Definition: message.h:2359
dpp::interaction_create_t::command
interaction command
command interaction
Definition: dispatcher.h:762
dpp::cluster::on_slashcommand
event_router_t< slashcommand_t > on_slashcommand
Called when a slash command is issued. Only dpp::ctxm_chat_input types of interaction are routed to t...
Definition: cluster.h:739
dpp::component::set_type
component & set_type(component_type ct)
Set the type of the component. Button components (type dpp::cot_button) should always be contained wi...
dpp::slashcommand
Represents an application command, created by your bot either globally, or on a guild.
Definition: appcommand.h:1416
dpp::when_any
Experimental class to co_await on a bunch of awaitable objects, resuming when the first one completes...
Definition: when_any.h:170
dpp::utility::cout_logger
std::function< void(const dpp::log_t &)> DPP_EXPORT cout_logger()
Get a default logger that outputs to std::cout. e.g.
Definition: dispatcher.h:265
dpp::cluster::on_ready
event_router_t< ready_t > on_ready
Called when a shard is connected and ready. A set of cluster::on_guild_create events will follow this...
Definition: cluster.h:847
dpp::component::set_id
component & set_id(std::string_view id)
Set the id of the component. For action rows, this field is ignored. Setting the id will auto-set the...
dpp::interaction::get_command_name
std::string get_command_name() const
Get the command name for a command interaction.
dpp::cluster::global_command_create
void global_command_create(const slashcommand &s, command_completion_event_t callback=utility::log_error())
Create a global slash command (a bot can have a maximum of 100 of these).
dpp::cluster::start
void start(start_type return_after=st_wait)
Start the cluster, connecting all its shards.
dpp::interaction_create_t::reply
void reply(command_completion_event_t callback=utility::log_error()) const
Acknowledge interaction without displaying a message to the user, for use with button and select menu...
dpp::cot_button
@ cot_button
Clickable button.
Definition: message.h:86
dpp::component
Represents the component object. A component is a clickable button or drop down list within a discord...
Definition: message.h:506
dpp::component::add_component
component & add_component(const component &c)
Add a sub-component, only valid for action rows. Adding subcomponents to a component will automatical...
dpp::cluster
The cluster class represents a group of shards and a command queue for sending and receiving commands...
Definition: cluster.h:108
dpp::button_click_t
Click on button.
Definition: dispatcher.h:787
dpp::unicode_emoji::m
constexpr const char m[]
Definition: unicode_emoji.h:5304
dpp::cluster::on_log
event_router_t< log_t > on_log
Called when a log message is to be written to the log. You can attach any logging system here you wis...
Definition: cluster.h:697
dpp::ready_t
Session ready.
Definition: dispatcher.h:1045
D++ Library version 9.0.13D++ Library version 9.0.12D++ Library version 9.0.11D++ Library version 9.0.10D++ Library version 9.0.9D++ Library version 9.0.8D++ Library version 9.0.7D++ Library version 9.0.6D++ Library version 9.0.5D++ Library version 9.0.4D++ Library version 9.0.3D++ Library version 9.0.2D++ Library version 9.0.1D++ Library version 9.0.0D++ Library version 1.0.2D++ Library version 1.0.1D++ Library version 1.0.0