Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

105 changed files with 80226 additions and 368 deletions

38
CMakeLists.txt Normal file
View File

@ -0,0 +1,38 @@
# Minimum CMake version required
cmake_minimum_required(VERSION 3.22)
# Project name, version, and description
project(discord-bot VERSION 1.0 DESCRIPTION "A discord bot" LANGUAGES CXX) # Specify CXX language
# Temporarily removed to rule out interference from custom CMake modules.
# If you have custom CMake modules here that you need, we can re-add this later.
# list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
# Create an executable
add_executable(${PROJECT_NAME}
src/main.cpp
src/ImageProcessor.cpp
)
# Include directories for your headers and D++ headers
# This tells the compiler where to find #include <ImageProcessor.h> and #include <dpp/dpp.h>
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include # This covers ImageProcessor.h and the parent for dpp/dpp.h
${CMAKE_CURRENT_SOURCE_DIR}/include/stb # For stb_image.h and stb_image_write.h
# Ensure this path is correct if your D++ headers are NOT directly in ${CMAKE_CURRENT_SOURCE_DIR}/include/dpp
# If dpp/dpp.h is found in ${CMAKE_CURRENT_SOURCE_DIR}/include/dpp, then the line below is correct.
${CMAKE_CURRENT_SOURCE_DIR}/include/dpp
)
# --- NEW: Direct Linker Injection using Generator Expression ---
# This method tells CMake to ONLY pass the full path to the linker,
# explicitly preventing it from being treated as a build target.
target_link_libraries(${PROJECT_NAME} PRIVATE
"$<LINK_ONLY:${CMAKE_CURRENT_SOURCE_DIR}/lib/libdpp.so.10.1.4>" # Corrected 'libs' to 'lib'
)
# Set C++ version
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)

View File

@ -1,46 +0,0 @@
# poomer-discord-hello-world
Displays an image in reverse on the discord server after dropping in on a discord channel
Follow instructions to create a bot for your discord server at https://dpp.dev/creating-a-bot-application.html The link will also show you how to create a bot token as well. Create a token.txt file in your workdir folder. Copy the bot token and paste it into token.txt.
## Usage
./discord-bot
# Build
```
workdir/
├── build_engine_sdk/
├── poomer-discord-hello-world/
└── lib
└── dpp # contains include files of D++
├── DPP/
├── makefile
├── README.md
└── token.txt
```
## Linux
### bella_engine_sdk
```
curl -O https://downloads.bellarender.com/bella_engine_sdk-24.6.0.tar.gz
tar -xvf bella_engine_sdk-24.6.0.tar.gz
```
```
mkdir workdir
cd workdir
git clone https://github.com/brainboxdotcc/DPP.git
cd DPP
cmake -B ./build
cmake --build ./build -j4
cd ..
git clone https://git.indoodle.com/oomer/poomer-discord-helloworld.git
cd poomer-discord-helloworld
```
For a prototype app we can just dump the stb headers right into the helloworld dir for simplicity
```
curl -LO https://raw.githubusercontent.com/nothings/stb/master/stb_image.h
curl -LO https://raw.githubusercontent.com/nothings/stb/master/stb_image_write.h
make all -j4
```

7
cmake/FindDPP.cmake Normal file
View File

@ -0,0 +1,7 @@
find_path(DPP_INCLUDE_DIR NAMES dpp/dpp.h HINTS ${DPP_ROOT_DIR})
find_library(DPP_LIBRARIES NAMES dpp "libdpp.a" HINTS ${DPP_ROOT_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(DPP DEFAULT_MSG DPP_LIBRARIES DPP_INCLUDE_DIR)

0
cmake/main.cpp Normal file
View File

0
cmake/main.h Normal file
View File

View File

@ -1,228 +0,0 @@
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <dpp/dpp.h>
#include <iostream>
#include <fstream> // Required for file operations
#include <vector> // Required for std::vector
#include <string>
#include "stb_image.h"
#include "stb_image_write.h"
using namespace std;
void flip_image_horizontally(unsigned char* data, int width, int height, int channels);
void flip_image_vertically(unsigned char* data, int width, int height, int channels);
void process_attachment(dpp::cluster& bot, const dpp::message_create_t& event, const dpp::attachment& attachment);
int main() {
// --- Load the bot token from a file ---
string token_file_path = "token.txt";
ifstream token_file(token_file_path);
string bot_token;
if (!token_file.is_open()) {
// if not, try parent directory
if (!token_file.is_open()) {
token_file_path = "../token.txt";
token_file.open(token_file_path);
if (!token_file.is_open())
{
cerr << "ERROR: Could not open token.txt. Please create the file and add your bot token." << std::endl;
return 1;
}
}
}
getline(token_file, bot_token);
token_file.close();
if (bot_token.empty()) {
std::cerr << "ERROR: Token file is empty. Please add your bot token to token.txt." << std::endl;
return 1;
}
dpp::cluster bot(bot_token, dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger()); // Corrected: no 'true' argument
std::cout<<"in main"<<endl;
// Create an instance of your ImageProcessor class
//ImageProcessor image_processor(bot);
bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) {
std::cout << "Received slash cmd" << std::endl << std::flush; // Keep for testing
bot.log(dpp::ll_info, "Received slash command: " + event.command.get_command_name());
if (event.command.get_command_name() == "ping") {
bot.log(dpp::ll_info, "Processing /ping command. Replying now!");
event.reply("Pong!");
} else {
bot.log(dpp::ll_info, "Received unknown slash command: " + event.command.get_command_name());
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
bot.log(dpp::ll_info, "Bot ready. Logged in as: " + bot.me.username);
if (dpp::run_once<struct register_bot_commands>()) {
bot.log(dpp::ll_info, "Registering global /ping command...");
bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id));
}
});
bot.on_message_create([&bot](const dpp::message_create_t& event) {
// Ignore messages from the bot itself to prevent infinite loops
bot.log(dpp::ll_info, "author id: " + to_string(event.msg.author.id));
bot.log(dpp::ll_info, "bot id: " + to_string(bot.me.id));
if (event.msg.author.id == bot.me.id) {
bot.log(dpp::ll_info, "Ignoring message, author and bot id match");
return;
}
// Check if the message has any attachments
if (!event.msg.attachments.empty()) { //
// Log that an attachment was found
bot.log(dpp::ll_info, "Message from " + event.msg.author.username + " contains attachments!");
// You can iterate through the attachments if you need details about each one
for (const auto& attachment : event.msg.attachments) { //
bot.log(dpp::ll_info, " Attachment ID: " + std::to_string(attachment.id) +
", Filename: " + attachment.filename +
", Size: " + std::to_string(attachment.size) + " bytes");
// You can also get the URL of the attachment: attachment.url
// And a proxied URL: attachment.proxy_url
std::string filename = attachment.filename;
if (filename.substr(strlen(filename.c_str())-3,3) == "jpg")
{
bot.log(dpp::ll_info, "jpg detected");
std::string local_filename = "temp_" + attachment.filename;
process_attachment(bot,event,attachment);
bot.log(dpp::ll_info, "image flipped horizontally");
// Download the attachment first
//download_file(bot, event, attachment.url, local_filename); //
// Note: The download is asynchronous. You would typically do
// further processing in the http_request_completion_t handler.
// For a simple example, you might call load_image from there.
}
}
// Example: Reply to the user saying you detected an attachment
// event.reply("Thanks for the attachment!");
} else {
// Log if no attachments were found
bot.log(dpp::ll_info, "Message from " + event.msg.author.username + " has no attachments.");
}
// You can also add logic for processing other types of messages or commands here
});
std::cerr << "--- Direct stderr test: Bot is about to start listening ---" << std::endl;
bot.start(dpp::st_wait);
return 0;
}
void process_attachment(dpp::cluster& bot, const dpp::message_create_t& event, const dpp::attachment& attachment) {
std::string local_filename = "temp_" + std::to_string(attachment.id) + "_" + attachment.filename;
bot.request(attachment.url, dpp::http_method::m_get,
[ &bot, event, local_filename, attachment](const dpp::http_request_completion_t& completion) {
if (completion.status < 400) {
std::ofstream file(local_filename, std::ios::binary);
if (file.is_open()) {
file.write(completion.body.c_str(), completion.body.length());
file.close();
int width, height, channels;
//unsigned char* image_data = stbi_load_from_memory(local_filename.c_str(), &width, &height, &channels, 0);
unsigned char* image_data = stbi_load_from_memory(
reinterpret_cast<const stbi_uc*>(completion.body.data()),
completion.body.length(),
&width, &height, &channels, 0
);
if (image_data) {
// Flip the image horizontally (you can choose which flip to apply)
bot.log(dpp::ll_info,"channels: " + std::to_string(channels));
flip_image_horizontally(image_data, width, height, channels);
// --- Save the flipped image to a new temporary file ---
std::string flipped_filename = "flipped_" + local_filename;
stbi_write_jpg(flipped_filename.c_str(), width, height, channels, image_data, 100); // Quality 100
// --- NEW CODE STARTS HERE ---
dpp::message msg;
// Add the file to the message
msg.add_file(flipped_filename, dpp::utility::read_file(flipped_filename));
// Create an embed and reference the attached file
dpp::embed embed;
embed.set_title("Image Info");
embed.set_description("Dimensions: " + std::to_string(width) + "x" + std::to_string(height));
embed.set_image("attachment://" + flipped_filename); // Reference the file here
// Add the embed to the message
msg.add_embed(embed);
msg.set_channel_id(1398043078559797310);
bot.message_create(msg);
msg.set_content("Here's your horizontally flipped image");
event.reply(msg);
// --- NEW CODE ENDS HERE ---
stbi_image_free(image_data);
std::remove(local_filename.c_str());
std::remove(flipped_filename.c_str()); // Clean up the flipped file
} else {
std::cerr << "Error: Failed to load image with stb from " << local_filename << std::endl;
}
std::remove(local_filename.c_str());
} else {
std::cerr << "Failed to open local file for writing: " << local_filename << std::endl;
}
} else {
std::cerr << "Failed to download image from " << attachment.url << ". HTTP status code: " << completion.status << std::endl;
}
});
}
void flip_image_horizontally(unsigned char* data, int width, int height, int channels) {
for (int y = 0; y < height; ++y) {
// Get pointers to the start of the current row
unsigned char* row_start = data + y * width * channels;
// Loop through half the row, swapping pixels from left and right
for (int x = 0; x < width / 2; ++x) {
// Calculate the pointers to the left and right pixels
unsigned char* left_pixel = row_start + x * channels;
unsigned char* right_pixel = row_start + (width - 1 - x) * channels;
// Swap each channel of the pixels
for (int c = 0; c < channels; ++c) {
std::swap(left_pixel[c], right_pixel[c]);
}
}
}
}
void flip_image_vertically(unsigned char* data, int width, int height, int channels) {
// ... (your vertical flip implementation from before)
int row_stride = width * channels;
unsigned char* temp_row = new unsigned char [row_stride];
for (int y = 0; y < height / 2; ++y) {
unsigned char* top_row = data + y * row_stride;
unsigned char* bottom_row = data + (height - 1 - y) * row_stride;
std::memcpy(temp_row, top_row, row_stride);
std::memcpy(top_row, bottom_row, row_stride);
std::memcpy(bottom_row, temp_row, row_stride);
}
delete[] temp_row;
}

1669
include/dpp/appcommand.h Normal file

File diff suppressed because it is too large Load Diff

523
include/dpp/application.h Normal file
View File

@ -0,0 +1,523 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/integration.h>
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/utility.h>
#include <dpp/user.h>
#include <dpp/guild.h>
#include <dpp/permissions.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
#include <map>
#include <optional>
namespace dpp {
/**
* @brief status of a member of a team who maintain a bot/application
*/
enum team_member_status : uint8_t {
/**
* @brief User was invited to the team.
*/
tms_invited = 1,
/**
* @brief User has accepted membership onto the team.
*/
tms_accepted = 2
};
/**
* @brief Flags for a bot or application
*/
enum application_flags : uint32_t {
/**
* @brief Indicates if an app uses the Auto Moderation API
*/
apf_application_automod_rule_create_badge = (1 << 6),
/**
* @brief Has gateway presence intent
*/
apf_gateway_presence = (1 << 12),
/**
* @brief Has gateway presence intent for <100 guilds
*/
apf_gateway_presence_limited = (1 << 13),
/**
* @brief Has guild members intent
*/
apf_gateway_guild_members = (1 << 14),
/**
* @brief Has guild members intent for <100 guilds
*/
apf_gateway_guild_members_limited = (1 << 15),
/**
* @brief Verification is pending
*/
apf_verification_pending_guild_limit = (1 << 16),
/**
* @brief Embedded
*/
apf_embedded = (1 << 17),
/**
* @brief Has approval for message content
*/
apf_gateway_message_content = (1 << 18),
/**
* @brief Has message content, but <100 guilds
*/
apf_gateway_message_content_limited = (1 << 19),
/**
* @brief Indicates if the app has registered global application commands
*/
apf_application_command_badge = (1 << 23)
};
/**
* @brief Represents the settings for the bot/application's in-app authorization link
*/
struct DPP_EXPORT application_install_params {
/**
* @brief A bitmask of dpp::permissions to request for the bot role.
*/
permission permissions;
/**
* @brief The scopes as strings to add the application to the server with.
*
* @see https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes
*/
std::vector<std::string> scopes;
};
/**
* @brief Team member role types for application team members.
*
* These are hard coded to string forms by discord. If further types are added,
* this enum will be extended to support them.
*/
enum team_member_role_t : uint8_t {
/**
* @brief Team owner.
*/
tmr_owner,
/**
* @brief Team admin.
*/
tmr_admin,
/**
* @brief Developer
*/
tmr_developer,
/**
* @brief Read-Only
*/
tmr_readonly,
};
/**
* @brief Represents a team member on a team who maintain a bot/application
*/
class DPP_EXPORT team_member {
public:
/**
* @brief The user's membership state on the team.
*/
team_member_status membership_state;
/**
* @brief Will always be "".
*/
std::string permissions;
/**
* @brief The id of the parent team of which they are a member.
*/
snowflake team_id;
/**
* @brief The avatar, discriminator, id, and username, of the user.
*/
user member_user;
/**
* @brief The role of the user in the team.
*/
team_member_role_t member_role;
};
/**
* @brief Represents a team of users who maintain a bot/application
*/
class DPP_EXPORT app_team {
public:
/**
* @brief A hash of the image of the team's icon (may be empty).
*/
utility::iconhash icon;
/**
* @brief The id of the team.
*/
snowflake id;
/**
* @brief The members of the team.
*/
std::vector<team_member> members;
/**
* @brief The name of the team.
*/
std::string name;
/**
* @brief The user id of the current team owner.
*/
snowflake owner_user_id;
};
/**
* @brief Status indicating whether event webhooks are enabled or disabled for an application.
*/
enum application_event_webhook_status: uint8_t {
/**
* @brief Webhook events are disabled by developer
*/
ews_disabled = 1,
/**
* @brief Webhook events are enabled by developer
*/
ews_enabled = 2,
/**
* @brief Webhook events are disabled by Discord, usually due to inactivity
*/
ews_disabled_by_discord = 3,
};
/**
* @brief Configuration object for an app installation
*/
struct DPP_EXPORT integration_configuration {
application_install_params oauth2_install_params;
};
/**
* @brief The application class represents details of a bot application
*/
class DPP_EXPORT application : public managed, public json_interface<application> {
protected:
friend struct json_interface<application>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
application& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief The name of the app.
*/
std::string name;
/**
* @brief The icon hash of the app (may be empty).
*/
utility::iconhash icon;
/**
* @brief The description of the app.
*/
std::string description;
/**
* @brief Optional: an array of rpc origin urls, if rpc is enabled.
*/
std::vector<std::string> rpc_origins;
/**
* @brief When false, only app owner add the bot to guilds.
*/
bool bot_public;
/**
* @brief When true, the app's bot will only join upon completion of the full oauth2 code grant flow
*/
bool bot_require_code_grant;
/**
* @brief Optional: Partial user object for the bot user associated with the app.
*/
user bot;
/**
* @brief Optional: the url of the app's terms of service.
*/
std::string terms_of_service_url;
/**
* @brief Optional: the url of the app's privacy policy.
*/
std::string privacy_policy_url;
/**
* @brief Optional: partial user object containing info on the owner of the application.
*/
user owner;
/**
* @brief If this application is a game sold on Discord, this field will be the summary field for the store page of its primary SKU.
*
* @deprecated Will be removed in v11
*/
std::string summary;
/**
* @brief The hex encoded key for verification in interactions and the GameSDK's GetTicket.
*/
std::string verify_key;
/**
* @brief If the application belongs to a team, this will be a list of the members of that team (may be empty).
*/
app_team team;
/**
* @brief Optional: if this application is a game sold on Discord, this field will be the guild to which it has been linked.
*/
snowflake guild_id;
/**
* @brief Partial object of the associated guild.
*/
guild guild_obj;
/**
* @brief Optional: if this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists.
*/
snowflake primary_sku_id;
/**
* @brief Optional: if this application is a game sold on Discord, this field will be the URL slug that links to the store page.
*/
std::string slug;
/**
* @brief Optional: the application's default rich presence invite cover image hash
*/
utility::iconhash cover_image;
/**
* @brief Optional: the application's public flags.
*/
uint32_t flags;
/**
* @brief Optional: Approximate count of guilds the app has been added to.
*/
uint64_t approximate_guild_count;
/**
* @brief Optional: Approximate count of users that have installed the app
*/
uint64_t approximate_user_install_count;
/**
* @brief Optional: Array of redirect URIs for the app.
*/
std::vector<std::string> redirect_uris;
/**
* @brief Optional: Interactions endpoint URL for the app.
*/
std::string interactions_endpoint_url;
/**
* @brief The application's role connection verification entry point
* which, when configured, will render the app as a verification method
* in the guild role verification configuration.
*/
std::string role_connections_verification_url;
/**
* @brief Event webhooks URL for the app to receive webhook events
*/
std::string event_webhooks_url;
/**
* @brief List of Webhook event types the app subscribes to.
*/
std::vector<std::string> event_webhooks_types;
/**
* If webhook events are enabled for the app.
*/
application_event_webhook_status event_webhooks_status;
/**
* @brief Up to 5 tags describing the content and functionality of the application.
*/
std::vector<std::string> tags;
/**
* @brief Settings for the application's default in-app authorization link, if enabled.
*/
application_install_params install_params;
/**
* @brief Default scopes and permissions for each supported installation context
*/
std::map<application_integration_types, integration_configuration> integration_types_config;
/**
* @brief The application's default custom authorization link, if enabled.
*/
std::string custom_install_url;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t discoverability_state;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint32_t discovery_eligibility_flags;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t explicit_content_filter;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t creator_monetization_state;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
bool integration_public;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
bool integration_require_code_grant;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
std::vector<std::string> interactions_event_types;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t interactions_version;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
bool is_monetized;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint32_t monetization_eligibility_flags;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t monetization_state;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
bool hook;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t rpc_application_state;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t store_application_state;
/**
* @warning This variable is not documented by discord, we have no idea what it means and how it works. Use at your own risk.
*/
uint8_t verification_state;
/** Constructor */
application();
/** Destructor */
~application();
/**
* @brief Get the application's cover image url if they have one, otherwise returns an empty string
*
* @param size The size of the cover image in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized cover image is returned.
* @param format The format to use for the avatar. It can be one of `i_webp`, `i_jpg` or `i_png`.
* @return std::string cover image url or an empty string, if required attributes are missing or an invalid format was passed
*/
std::string get_cover_image_url(uint16_t size = 0, const image_type format = i_png) const;
/**
* @brief Get the application's icon url if they have one, otherwise returns an empty string
*
* @param size The size of the icon in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized icon is returned.
* @param format The format to use for the avatar. It can be one of `i_webp`, `i_jpg` or `i_png`.
* @return std::string icon url or an empty string, if required attributes are missing or an invalid format was passed
*/
std::string get_icon_url(uint16_t size = 0, const image_type format = i_png) const;
};
/**
* @brief A group of applications.
*
* This is not currently ever sent by Discord API but the DPP standard setup for
* objects that can be received by REST has the possibility for this, so this exists.
* Don't ever expect to see one at present.
*/
typedef std::unordered_map<snowflake, application> application_map;
}

481
include/dpp/auditlog.h Normal file
View File

@ -0,0 +1,481 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
#include <optional>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Defines types of audit log entry
*/
enum audit_type {
/**
* @brief Guild update
*/
aut_guild_update = 1,
/**
* @brief Channel create
*/
aut_channel_create = 10,
/**
* @brief Channel update
*/
aut_channel_update = 11,
/**
* @brief Channel delete
*/
aut_channel_delete = 12,
/**
* @brief Channel overwrite create
*/
aut_channel_overwrite_create = 13,
/**
* @brief Channel overwrite update
*/
aut_channel_overwrite_update = 14,
/**
* @brief Channel overwrite delete
*/
aut_channel_overwrite_delete = 15,
/**
* @brief Channel member kick
*/
aut_member_kick = 20,
/**
* @brief Channel member prune
*/
aut_member_prune = 21,
/**
* @brief Channel member ban add
*/
aut_member_ban_add = 22,
/**
* @brief Channel member ban remove
*/
aut_member_ban_remove = 23,
/**
* @brief Guild member update
*/
aut_member_update = 24,
/**
* @brief Guild member role update
*/
aut_member_role_update = 25,
/**
* @brief Guild member move
*/
aut_member_move = 26,
/**
* @brief Guild member voice disconnect
*/
aut_member_disconnect = 27,
/**
* @brief Guild bot add
*/
aut_bot_add = 28,
/**
* @brief Guild role create
*/
aut_role_create = 30,
/**
* @brief Guild role update
*/
aut_role_update = 31,
/**
* @brief Guild role delete
*/
aut_role_delete = 32,
/**
* @brief Guild invite create
*/
aut_invite_create = 40,
/**
* @brief Guild invite update
*/
aut_invite_update = 41,
/**
* @brief Guild invite delete
*/
aut_invite_delete = 42,
/**
* @brief Guild webhook create
*/
aut_webhook_create = 50,
/**
* @brief Guild webhook update
*/
aut_webhook_update = 51,
/**
* @brief Guild webhook delete
*/
aut_webhook_delete = 52,
/**
* @brief Guild emoji create
*/
aut_emoji_create = 60,
/**
* @brief Guild emoji update
*/
aut_emoji_update = 61,
/**
* @brief Guild emoji delete
*/
aut_emoji_delete = 62,
/**
* @brief Guild message delete
*/
aut_message_delete = 72,
/**
* @brief Guild message bulk delete
*/
aut_message_bulk_delete = 73,
/**
* @brief Guild message pin
*/
aut_message_pin = 74,
/**
* @brief Guild message unpin
*/
aut_message_unpin = 75,
/**
* @brief Guild integration create
*/
aut_integration_create = 80,
/**
* @brief Guild integration update
*/
aut_integration_update = 81,
/**
* @brief Guild integration delete
*/
aut_integration_delete = 82,
/**
* @brief Stage instance create
*/
aut_stage_instance_create = 83,
/**
* @brief Stage instance update
*/
aut_stage_instance_update = 84,
/**
* @brief stage instance delete
*/
aut_stage_instance_delete = 85,
/**
* @brief Sticker create
*/
aut_sticker_create = 90,
/**
* @brief Sticker update
*/
aut_sticker_update = 91,
/**
* @brief Sticker delete
*/
aut_sticker_delete = 92,
/**
* @brief Scheduled event creation
*/
aut_guild_scheduled_event_create = 100,
/**
* @brief Scheduled event update
*/
aut_guild_scheduled_event_update = 101,
/**
* @brief Scheduled event deletion
*/
aut_guild_scheduled_event_delete = 102,
/**
* @brief Thread create
*/
aut_thread_create = 110,
/**
* @brief Thread update
*/
aut_thread_update = 111,
/**
* @brief Thread delete
*/
aut_thread_delete = 112,
/**
* @brief Application command permissions update
*/
aut_appcommand_permission_update = 121,
/**
* @brief Auto moderation rule creation
*/
aut_automod_rule_create = 140,
/**
* @brief Auto moderation rule update
*/
aut_automod_rule_update = 141,
/**
* @brief Auto moderation rule deletion
*/
aut_automod_rule_delete = 142,
/**
* @brief Message was blocked by Auto Moderation
*/
aut_automod_block_message = 143,
/**
* @brief Message was flagged by Auto Moderation
*/
aut_automod_flag_to_channel = 144,
/**
* @brief Member was timed out by Auto Moderation
*/
aut_automod_user_communication_disabled = 145,
/**
* @brief Creator monetization request was created
*/
aut_creator_monetization_request_created = 150,
/**
* @brief Creator monetization terms were accepted
*/
aut_creator_monetization_terms_accepted = 151,
};
/**
* @brief Defines audit log changes
*/
struct DPP_EXPORT audit_change {
/**
* @brief Optional: Serialised new value of the change, e.g. for nicknames, the new nickname.
*/
std::string new_value;
/**
* @brief Optional: Serialised old value of the change, e.g. for nicknames, the old nickname.
*/
std::string old_value;
/**
* @brief The property name that was changed (e.g. `nick` for nickname changes).
* @note For dpp::aut_appcommand_permission_update updates the key is the id of the user, channel, role, or a permission constant that was updated instead of an actual property name.
*/
std::string key;
};
/**
* @brief Extra information for an audit log entry
*/
struct DPP_EXPORT audit_extra {
/**
* @brief Name of the Auto Moderation rule that was triggered.
*/
std::string automod_rule_name;
/**
* @brief Trigger type of the Auto Moderation rule that was triggered.
*/
std::string automod_rule_trigger_type;
/**
* @brief Number of days after which inactive members were kicked.
*/
std::string delete_member_days;
/**
* @brief Number of members removed by the prune.
*/
std::string members_removed;
/**
* @brief Channel in which the entities were targeted.
*/
snowflake channel_id;
/**
* @brief ID of the message that was targeted.
*/
snowflake message_id;
/**
* @brief Number of entities that were targeted.
*/
std::string count;
/**
* @brief ID of the overwritten entity.
*/
snowflake id;
/**
* @brief Type of overwritten entity - "0" for "role" or "1" for "member"
*/
std::string type;
/**
* @brief Name of the role if type is "0" (not present if type is "1").
*/
std::string role_name;
/**
* @brief ID of the app whose permissions were targeted
*/
snowflake application_id;
};
/**
* @brief An individual audit log entry
*/
struct DPP_EXPORT audit_entry : public json_interface<audit_entry> {
protected:
friend struct json_interface<audit_entry>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
audit_entry& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief ID of the entry.
*/
snowflake id;
/**
* ID of the affected entity (webhook, user, role, etc.) (may be empty)
* @note For dpp::audit_type::aut_appcommand_permission_update updates, it's the command ID or the app ID
*/
snowflake target_id;
/**
* @brief Optional: changes made to the target_id.
*/
std::vector<audit_change> changes;
/**
* @brief The user or app that made the changes (may be empty).
*/
snowflake user_id;
/**
* @brief Type of action that occurred.
*/
audit_type type;
/**
* @brief Optional: additional info for certain action types.
*/
std::optional<audit_extra> extra;
/**
* @brief Optional: the reason for the change (1-512 characters).
*/
std::string reason;
/** Constructor */
audit_entry();
/** Destructor */
virtual ~audit_entry() = default;
};
/**
* @brief The auditlog class represents the audit log entries of a guild.
*/
class DPP_EXPORT auditlog : public json_interface<auditlog> {
protected:
friend struct json_interface<auditlog>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
auditlog& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief Audit log entries.
*/
std::vector<audit_entry> entries;
/** Constructor */
auditlog() = default;
/** Destructor */
virtual ~auditlog() = default;
};
}

403
include/dpp/automod.h Normal file
View File

@ -0,0 +1,403 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/utility.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Possible types of preset filter lists
*/
enum automod_preset_type : uint8_t {
/**
* @brief Strong swearing
*/
amod_preset_profanity = 1,
/**
* @brief Sexual phrases and words
*/
amod_preset_sexual_content = 2,
/**
* @brief Racial and other slurs, hate speech
*/
amod_preset_slurs = 3,
};
/**
* @brief Action types to perform on filtering
*/
enum automod_action_type : uint8_t {
/**
* @brief Blocks the message and prevents it from being posted.
* A custom explanation can be specified and shown to members whenever their message is blocked
*/
amod_action_block_message = 1,
/**
* @brief Send an alert to a given channel
*/
amod_action_send_alert = 2,
/**
* @brief timeout the user
* @note Can only be set up for rules with trigger types of dpp::amod_type_keyword and dpp::amod_type_mention_spam
*/
amod_action_timeout = 3,
};
/**
* @brief Event types, only message send is currently supported
*/
enum automod_event_type : uint8_t {
/**
* @brief Trigger on message send or edit
*/
amod_message_send = 1,
};
/**
* @brief Types of moderation to trigger
*/
enum automod_trigger_type : uint8_t {
/**
* @brief Check if content contains words from a user defined list of keywords (max 6 of this type per guild)
*/
amod_type_keyword = 1,
/**
* @brief Harmful/malware links
* @deprecated Removed by Discord
*/
amod_type_harmful_link = 2,
/**
* @brief Check if content represents generic spam (max 1 of this type per guild)
*/
amod_type_spam = 3,
/**
* @brief Check if content contains words from discord pre-defined wordsets (max 1 of this type per guild)
*/
amod_type_keyword_preset = 4,
/**
* @brief Check if content contains more mentions than allowed (max 1 of this type per guild)
*/
amod_type_mention_spam = 5,
};
/**
* @brief Metadata associated with an automod action. Different fields are relevant based on the value of dpp::automod_rule::trigger_type.
*/
struct DPP_EXPORT automod_metadata : public json_interface<automod_metadata> {
protected:
friend struct json_interface<automod_metadata>;
/**
* @brief Fill object properties from JSON
*
* @param j JSON to fill from
* @return automod_metadata& Reference to self
*/
automod_metadata& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build a json for this object
*
* @return json JSON object
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief @brief Substrings which will be searched for in content (Maximum of 1000).
*
* Each keyword can be a phrase which contains multiple words.
* All keywords are case insensitive and can be up to 60 characters.
*
* Wildcard symbols (`*`) can be used to customize how each keyword will be matched.
*
* **Examples for the `*` wildcard symbol:**
*
* Prefix - word must start with the keyword
*
* | keyword | matches |
* |----------|-------------------------------------|
* | cat* | <u><b>cat</b></u>ch, <u><b>Cat</b></u>apult, <u><b>CAt</b></u>tLE |
* | the mat* | <u><b>the mat</b></u>rix |
*
* Suffix - word must end with the keyword
*
* | keyword | matches |
* |----------|--------------------------|
* | *cat | wild<u><b>cat</b></u>, copy<u><b>Cat</b></u> |
* | *the mat | brea<u><b>the mat</b></u> |
*
* Anywhere - keyword can appear anywhere in the content
*
* | keyword | matches |
* |-----------|-----------------------------|
* | \*cat* | lo<u><b>cat</b></u>ion, edu<u><b>Cat</b></u>ion |
* | \*the mat* | brea<u><b>the mat</b></u>ter |
*
* Whole Word - keyword is a full word or phrase and must be surrounded by whitespace at the beginning and end
*
* | keyword | matches |
* |---------|-------------|
* | cat | <u><b>Cat</b></u> |
* | the mat | <u><b>the mat</b></u> |
*
*/
std::vector<std::string> keywords;
/**
* @brief Regular expression patterns which will be matched against content (Maximum of 10).
*
* Only Rust flavored regex is currently supported, which can be tested in online editors such as [Rustexp](https://rustexp.lpil.uk/).
* Each regex pattern can be up to 260 characters.
*/
std::vector<std::string> regex_patterns;
/**
* @brief Preset keyword list types to moderate
* @see automod_preset_type
*/
std::vector<automod_preset_type> presets;
/**
* @brief Substrings which should not trigger the rule (Maximum of 100 for the trigger type dpp::amod_type_keyword, Maximum of 1000 for the trigger type dpp::amod_type_keyword_preset).
*
* Each keyword can be a phrase which contains multiple words.
* All keywords are case insensitive and can be up to 60 characters.
*
* Wildcard symbols (`*`) can be used to customize how each keyword will be matched.
*
* **Examples for the `*` wildcard symbol:**
*
* Prefix - word must start with the keyword
*
* | keyword | matches |
* |----------|-------------------------------------|
* | cat* | <u><b>cat</b></u>ch, <u><b>Cat</b></u>apult, <u><b>CAt</b></u>tLE |
* | the mat* | <u><b>the mat</b></u>rix |
*
* Suffix - word must end with the keyword
*
* | keyword | matches |
* |----------|--------------------------|
* | *cat | wild<u><b>cat</b></u>, copy<u><b>Cat</b></u> |
* | *the mat | brea<u><b>the mat</b></u> |
*
* Anywhere - keyword can appear anywhere in the content
*
* | keyword | matches |
* |-----------|-----------------------------|
* | \*cat* | lo<u><b>cat</b></u>ion, edu<u><b>Cat</b></u>ion |
* | \*the mat* | brea<u><b>the mat</b></u>ter |
*
* Whole Word - keyword is a full word or phrase and must be surrounded by whitespace at the beginning and end
*
* | keyword | matches |
* |---------|-------------|
* | cat | <u><b>Cat</b></u> |
* | the mat | <u><b>the mat</b></u> |
*
*/
std::vector<std::string> allow_list;
/**
* @brief Total number of unique role and user mentions allowed per message (Maximum of 50)
*/
uint8_t mention_total_limit;
/**
* @brief Whether to automatically detect mention raids
*/
bool mention_raid_protection_enabled;
/**
* @brief Construct a new automod metadata object
*/
automod_metadata();
/**
* @brief Destroy the automod metadata object
*/
virtual ~automod_metadata();
};
/**
* @brief Represents an automod action
*/
struct DPP_EXPORT automod_action : public json_interface<automod_action> {
protected:
friend struct json_interface<automod_action>;
/**
* @brief Fill object properties from JSON
*
* @param j JSON to fill from
* @return automod_action& Reference to self
*/
automod_action& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build a json for this object
*
* @return json JSON object
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Type of action to take
*/
automod_action_type type;
/**
* @brief Channel ID to which user content should be logged, for type dpp::amod_action_send_alert
*/
snowflake channel_id;
/**
* @brief Additional explanation that will be shown to members whenever their message is blocked. For type dpp::amod_action_block_message
*/
std::string custom_message;
/**
* @brief Timeout duration in seconds (Maximum of 2419200), for dpp::amod_action_timeout
*/
uint32_t duration_seconds;
/**
* @brief Construct a new automod action object
*/
automod_action();
/**
* @brief Destroy the automod action object
*/
virtual ~automod_action();
};
/**
* @brief Represents an automod rule
*/
class DPP_EXPORT automod_rule : public managed, public json_interface<automod_rule> {
protected:
friend struct json_interface<automod_rule>;
/**
* @brief Fill object properties from JSON
*
* @param j JSON to fill from
* @return automod_rule& Reference to self
*/
automod_rule& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build a json string for this object
*
* @return json JSON object
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief the id of this rule
*/
snowflake id;
/**
* @brief the guild which this rule belongs to
*/
snowflake guild_id;
/**
* @brief the rule name
*/
std::string name;
/**
* @brief The user which first created this rule
*/
snowflake creator_id;
/**
* @brief The rule event type
*/
automod_event_type event_type;
/**
* @brief The rule trigger type
*/
automod_trigger_type trigger_type;
/**
* @brief The rule trigger metadata
*/
automod_metadata trigger_metadata;
/**
* @brief the actions which will execute when the rule is triggered
*/
std::vector<automod_action> actions;
/**
* @brief Whether the rule is enabled
*/
bool enabled;
/**
* @brief the role ids that should not be affected by the rule (Maximum of 20)
*/
std::vector<snowflake> exempt_roles;
/**
* @brief the channel ids that should not be affected by the rule (Maximum of 50)
*/
std::vector<snowflake> exempt_channels;
/**
* @brief Construct a new automod rule object
*/
automod_rule();
/**
* @brief Destroy the automod rule object
*/
virtual ~automod_rule();
};
/** A group of automod rules.
*/
typedef std::unordered_map<snowflake, automod_rule> automod_rule_map;
}

69
include/dpp/ban.h Normal file
View File

@ -0,0 +1,69 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
#include <unordered_map>
namespace dpp {
/**
* @brief The ban class represents a ban on a guild.
*
*/
class DPP_EXPORT ban : public json_interface<ban> {
protected:
friend struct json_interface<ban>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
ban& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief The ban reason.
*/
std::string reason;
/**
* @brief User ID the ban applies to.
*/
snowflake user_id;
/** Constructor */
ban();
/** Destructor */
virtual ~ban() = default;
};
/**
* @brief A group of bans. The key is the user ID.
*/
typedef std::unordered_map<snowflake, ban> ban_map;
}

101
include/dpp/bignum.h Normal file
View File

@ -0,0 +1,101 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <memory>
namespace dpp {
/**
* @brief This contains the OpenSSL structs. It is not public,
* so that the public interface doesn't depend on OpenSSL directly.
*/
struct openssl_bignum;
/**
* @brief An arbitrary length integer number.
* Officially, the Discord documentation says that permission values can be any arbitrary
* number of digits. At time of writing there are only 50 bits of permissions, but this is
* set to grow larger and potentially past 64 bits. They will continue to send this data
* as a huge single integer at that point, because this is obviously sensible. /s
*
* @note dpp::bignumber uses OpenSSL BN_* under the hood, as we include openssl anyway
* for HTTPS.
*/
class DPP_EXPORT bignumber {
/**
* @brief Internal opaque struct to contain OpenSSL things
*/
std::shared_ptr<openssl_bignum> ssl_bn{nullptr};
public:
/**
* @brief Construct a new bignumber object
*/
bignumber() = default;
/**
* @brief Parse a std::string of an arbitrary length number into
* a bignumber.
* @param number_string string representation of a number. The
* number must be an integer, and can be positive or negative.
* @note Prefixing number_string with 0x will parse it as hexadecimal.
* This is not case sensitive.
*/
bignumber(const std::string& number_string);
/**
* @brief Build a bignumber from a vector of 64 bit values.
* The values are accepted in "reverse order", so the first vector
* entry at index 0 is the leftmost 64 bits of the bignum.
* The vector can be any arbitrary length.
* @param bits Vector of 64 bit values which represent the number
*/
bignumber(std::vector<uint64_t> bits);
/**
* @brief Default destructor
*/
~bignumber() = default;
/**
* @brief Get the string representation of the bignumber.
* @param hex If false (the default) the number is returned in
* decimal, else if this parameter is true, it will be returned
* as hex (without leading '0x')
* @return String representation of bignumber
*/
[[nodiscard]] std::string get_number(bool hex = false) const;
/**
* @brief Get the array of 64 bit values that represents the
* bignumber. This is what we should use to store bignumbers
* in memory, not this bignumber class itself, as the bignumber
* class instantiates OpenSSL structs and takes significantly
* more ram than just a vector.
* @return Vector of 64 bit values representing the bignumber
*/
[[nodiscard]] std::vector<uint64_t> get_binary() const;
};
}

274
include/dpp/cache.h Normal file
View File

@ -0,0 +1,274 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <unordered_map>
#include <mutex>
#include <shared_mutex>
namespace dpp {
extern DPP_EXPORT std::unordered_map<managed*, time_t> deletion_queue;
extern DPP_EXPORT std::mutex deletion_mutex;
/** forward declaration */
class guild_member;
/**
* @brief A cache object maintains a cache of dpp::managed objects.
*
* This is for example users, channels or guilds. You may instantiate
* your own caches, to contain any type derived from dpp::managed including
* your own types.
*
* @note This class is critical to the operation of the library and therefore
* designed with thread safety in mind.
* @tparam T class type to store, which should be derived from dpp::managed.
*/
template<class T> class cache {
private:
/**
* @brief Mutex to protect the cache
*
* This is a shared mutex so reading is cheap.
*/
std::shared_mutex cache_mutex;
/**
* @brief Container of pointers to cached items
*/
std::unordered_map<snowflake, T*>* cache_map;
public:
/**
* @brief Construct a new cache object.
*
* @note Caches must contain classes derived from dpp::managed.
*/
cache() {
cache_map = new std::unordered_map<snowflake, T*>;
}
/**
* @brief Destroy the cache object
*
* @note This does not delete objects stored in the cache.
*/
~cache() {
std::unique_lock l(cache_mutex);
delete cache_map;
}
/**
* @brief Store an object in the cache. Passing a nullptr will have no effect.
*
* The object must be derived from dpp::managed and should be allocated on the heap.
* Generally this is done via `new`. Once stored in the cache the lifetime of the stored
* object is managed by the cache class unless the cache is deleted (at which point responsibility
* for deleting the object returns to its allocator). Objects stored are removed when the
* cache::remove() method is called by placing them into a garbage collection queue for deletion
* within the next 60 seconds, which are then deleted in bulk for efficiency and to aid thread
* safety.
*
* @note Adding an object to the cache with an ID which already exists replaces that entry.
* The previously entered cache item is inserted into the garbage collection queue for deletion
* similarly to if cache::remove() was called first.
*
* @param object object to store. Storing a pointer to the cache relinquishes ownership to the cache object.
*/
void store(T* object) {
if (!object) {
return;
}
std::unique_lock l(cache_mutex);
auto existing = cache_map->find(object->id);
if (existing == cache_map->end()) {
(*cache_map)[object->id] = object;
} else if (object != existing->second) {
/* Flag old pointer for deletion and replace */
std::lock_guard<std::mutex> delete_lock(deletion_mutex);
deletion_queue[existing->second] = time(nullptr);
(*cache_map)[object->id] = object;
}
}
/**
* @brief Remove an object from the cache.
*
* @note The cache class takes ownership of the pointer, and calling this method will
* cause deletion of the object within the next 60 seconds by means of a garbage
* collection queue. This queue aids in efficiency by freeing memory in bulk, and
* assists in thread safety by ensuring that all deletions can be locked and freed
* at the same time.
*
* @param object object to remove. Passing a nullptr will have no effect.
*/
void remove(T* object) {
if (!object) {
return;
}
std::unique_lock l(cache_mutex);
std::lock_guard<std::mutex> delete_lock(deletion_mutex);
auto existing = cache_map->find(object->id);
if (existing != cache_map->end()) {
cache_map->erase(existing);
deletion_queue[object] = time(nullptr);
}
}
/**
* @brief Find an object in the cache by id.
*
* The cache is searched for the object. All dpp::managed objects have a snowflake id
* (this is the only field dpp::managed actually has).
*
* @warning Do not hang onto objects returned by cache::find() indefinitely. They may be
* deleted at a later date if cache::remove() is called. If persistence is required,
* take a copy of the object after checking its pointer is non-null.
*
* @param id Object snowflake id to find
* @return Found object or nullptr if the object with this id does not exist.
*/
T* find(snowflake id) {
std::shared_lock l(cache_mutex);
auto r = cache_map->find(id);
if (r != cache_map->end()) {
return r->second;
}
return nullptr;
}
/**
* @brief Return a count of the number of items in the cache.
*
* This is used by the library e.g. to count guilds, users, and roles
* stored within caches.
* get
* @return uint64_t count of items in the cache
*/
uint64_t count() {
std::shared_lock l(cache_mutex);
return cache_map->size();
}
/**
* @brief Return the cache's locking mutex.
*
* Use this whenever you manipulate or iterate raw elements in the cache!
*
* @note If you are only reading from the cache's container, wrap this
* mutex in `std::shared_lock`, else wrap it in a `std::unique_lock`.
* Shared locks will allow for multiple readers whilst blocking writers,
* and unique locks will allow only one writer whilst blocking readers
* and writers.
*
* **Example:**
*
* ```cpp
* dpp::cache<guild>* c = dpp::get_guild_cache();
* std::unordered_map<snowflake, guild*>& gc = c->get_container();
* std::shared_lock l(c->get_mutex()); // MUST LOCK HERE
* for (auto g = gc.begin(); g != gc.end(); ++g) {
* dpp::guild* gp = (dpp::guild*)g->second;
* // Do something here with the guild* in 'gp'
* }
* ```
*
* @return The mutex used to protect the container
*/
std::shared_mutex& get_mutex() {
return this->cache_mutex;
}
/**
* @brief Get the container unordered map
*
* @warning Be sure to use cache::get_mutex() correctly if you
* manipulate or iterate the map returned by this method! If you do
* not, this is not thread safe and will cause crashes!
*
* @see cache::get_mutex
*
* @return A reference to the cache's container map
*/
auto & get_container() {
return *(this->cache_map);
}
/**
* @brief "Rehash" a cache by reallocating the map and copying
* all elements into the new one.
*
* Over a long running timeframe, unordered maps can grow in size
* due to bucket allocation, this function frees that unused memory
* to keep the maps in control over time. If this is an issue which
* is apparent with your use of dpp::cache objects, you should periodically
* call this method.
*
* @warning May be time consuming! This function is O(n) in relation to the
* number of cached entries.
*/
void rehash() {
std::unique_lock l(cache_mutex);
std::unordered_map<snowflake, T*>* n = new std::unordered_map<snowflake, T*>;
n->reserve(cache_map->size());
for (auto t = cache_map->begin(); t != cache_map->end(); ++t) {
n->insert(*t);
}
delete cache_map;
cache_map = n;
}
/**
* @brief Get "real" size in RAM of the cached objects
*
* This does not include metadata used to maintain the unordered map itself.
*
* @return size_t size of cache in bytes
*/
size_t bytes() {
std::shared_lock l(cache_mutex);
return sizeof(*this) + (cache_map->bucket_count() * sizeof(size_t));
}
};
/**
* Run garbage collection across all caches removing deleted items
* that have been deleted over 60 seconds ago.
*/
void DPP_EXPORT garbage_collection();
#define cache_decl(type, setter, getter, counter) /** Find an object in the cache by id. @return type* Pointer to the object or nullptr when it's not found */ DPP_EXPORT class type * setter (snowflake id); DPP_EXPORT cache<class type> * getter (); /** Get the amount of cached type objects. */ DPP_EXPORT uint64_t counter ();
/* Declare major caches */
cache_decl(user, find_user, get_user_cache, get_user_count);
cache_decl(guild, find_guild, get_guild_cache, get_guild_count);
cache_decl(role, find_role, get_role_cache, get_role_count);
cache_decl(channel, find_channel, get_channel_cache, get_channel_count);
cache_decl(emoji, find_emoji, get_emoji_cache, get_emoji_count);
}

896
include/dpp/channel.h Normal file
View File

@ -0,0 +1,896 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/utility.h>
#include <dpp/voicestate.h>
#include <dpp/json_fwd.h>
#include <dpp/permissions.h>
#include <dpp/json_interface.h>
#include <unordered_map>
#include <variant>
namespace dpp {
/** @brief Flag integers as received from and sent to discord */
enum channel_type : uint8_t {
/**
* @brief A text channel within a server.
*/
CHANNEL_TEXT = 0,
/**
* @brief A direct message between users.
*/
DM = 1,
/**
* @brief A voice channel within a server.
*/
CHANNEL_VOICE = 2,
/**
* @brief a direct message between multiple users
* @deprecated this channel type was intended to be used with the now deprecated GameBridge SDK.
* Existing group dms with bots will continue to function, but newly created channels will be unusable.
*/
GROUP_DM = 3,
/**
* @brief An organizational category that contains up to 50 channels.
*/
CHANNEL_CATEGORY = 4,
/**
* @brief A channel that users can follow and cross-post into their own server.
*/
CHANNEL_ANNOUNCEMENT = 5,
/**
* @brief A channel in which game developers can sell their game on Discord.
* @deprecated Store channels are deprecated by Discord.
*/
CHANNEL_STORE = 6,
/**
* @brief A temporary sub-channel within a `GUILD_ANNOUNCEMENT` channel.
*/
CHANNEL_ANNOUNCEMENT_THREAD = 10,
/**
* @brief A temporary sub-channel within a `GUILD_TEXT` or `GUILD_FORUM` channel.
*/
CHANNEL_PUBLIC_THREAD = 11,
/**
* @brief A temporary sub-channel within a `GUILD_TEXT` channel
* that is only viewable by those invited and those with the `MANAGE_THREADS` permission.
*/
CHANNEL_PRIVATE_THREAD = 12,
/**
* @brief A "stage" channel, like a voice channel with one authorised speaker.
*/
CHANNEL_STAGE = 13,
/**
* @brief The channel in a hub containing the listed servers.
*
* @see https://support.discord.com/hc/en-us/articles/4406046651927-Discord-Student-Hubs-FAQ
*/
CHANNEL_DIRECTORY = 14,
/**
* @brief Forum channel that can only contain threads.
*/
CHANNEL_FORUM = 15,
/**
* @brief Media channel that can only contain threads, similar to forum channels.
*/
CHANNEL_MEDIA = 16,
};
/** @brief Our flags as stored in the object
* @note The bottom four bits of this flag are reserved to contain the channel_type values
* listed above as provided by Discord. If discord add another value > 15, we will have to
* shuffle these values upwards by one bit.
*/
enum channel_flags : uint16_t {
/* Note that bits 1 to 4 are used for the channel type mask */
/**
* @brief NSFW Gated Channel
*/
c_nsfw = 0b0000000000010000,
/**
* @brief Video quality forced to 720p
*/
c_video_quality_720p = 0b0000000000100000,
/**
* @brief Lock permissions (only used when updating channel positions)
*/
c_lock_permissions = 0b0000000001000000,
/**
* @brief Thread is pinned to the top of its parent forum or media channel
*/
c_pinned_thread = 0b0000000010000000,
/**
* @brief Whether a tag is required to be specified when creating a thread in a forum or media channel.
* Tags are specified in the thread::applied_tags field.
*/
c_require_tag = 0b0000000100000000,
/* Note that the 9th and 10th bit are used for the forum layout type. */
/**
* @brief When set hides the embedded media download options. Available only for media channels
*/
c_hide_media_download_options = 0b0001000000000000,
};
/**
* @brief Types for sort posts in a forum channel
*/
enum default_forum_sort_order_t : uint8_t {
/**
* @brief Sort forum posts by activity (default)
*/
so_latest_activity = 0,
/**
* @brief Sort forum posts by creation time (from most recent to oldest)
*/
so_creation_date = 1,
};
/**
* @brief Types of forum layout views that indicates how the threads in a forum channel will be displayed for users by default
*/
enum forum_layout_type : uint8_t {
/**
* @brief No default has been set for the forum channel
*/
fl_not_set = 0,
/**
* @brief Display posts as a list
*/
fl_list_view = 1,
/**
* @brief Display posts as a collection of tiles
*/
fl_gallery_view = 2,
};
/**
* @brief channel permission overwrite types
*/
enum overwrite_type : uint8_t {
/**
* @brief Role
*/
ot_role = 0,
/**
* @brief Member
*/
ot_member = 1
};
/**
* @brief Channel permission overwrites
*/
struct DPP_EXPORT permission_overwrite {
/**
* @brief ID of the role or the member
*/
snowflake id;
/**
* @brief Bitmask of allowed permissions
*/
permission allow;
/**
* @brief Bitmask of denied permissions
*/
permission deny;
/**
* @brief Type of overwrite. See dpp::overwrite_type
*/
uint8_t type;
/**
* @brief Construct a new permission_overwrite object
*/
permission_overwrite();
/**
* @brief Construct a new permission_overwrite object
* @param id ID of the role or the member to create the overwrite for
* @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) for this user/role in this channel
* @param deny Bitmask of denied permissions (refer to enum dpp::permissions) for this user/role in this channel
* @param type Type of overwrite
*/
permission_overwrite(snowflake id, uint64_t allow, uint64_t deny, overwrite_type type);
};
/**
* @brief Auto archive duration of threads which will stop showing in the channel list after the specified period of inactivity.
* Defined as an enum to fit into 1 byte. Internally it'll be translated to minutes to match the API
*/
enum auto_archive_duration_t : uint8_t {
/**
* @brief Auto archive duration of 1 hour (60 minutes).
*/
arc_1_hour = 1,
/**
* @brief Auto archive duration of 1 day (1440 minutes).
*/
arc_1_day = 2,
/**
* @brief Auto archive duration of 3 days (4320 minutes).
*/
arc_3_days = 3,
/**
* @brief Auto archive duration of 1 week (10080 minutes).
*/
arc_1_week = 4,
};
/**
* @brief Represents a tag that is able to be applied to a thread in a forum or media channel
*/
struct DPP_EXPORT forum_tag : public managed, public json_interface<forum_tag> {
protected:
friend struct json_interface<forum_tag>;
/**
* @brief Read struct values from a json object
* @param j json to read values from
* @return A reference to self
*/
forum_tag& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build json for this forum_tag object
*
* @param with_id include the ID in the json
* @return json JSON object
*/
json to_json_impl(bool with_id = false) const;
public:
/**
* @brief The name of the tag (0-20 characters).
*/
std::string name;
/**
* @brief The emoji of the tag.
* Contains either nothing, the id of a guild's custom emoji or the unicode character of the emoji.
*/
std::variant<std::monostate, snowflake, std::string> emoji;
/**
* @brief Whether this tag can only be added to or removed from threads
* by a member with the `MANAGE_THREADS` permission.
*/
bool moderated;
/** Constructor */
forum_tag();
/**
* @brief Constructor
*
* @param name The name of the tag. It will be truncated to the maximum length of 20 UTF-8 characters.
*/
forum_tag(const std::string& name);
/** Destructor */
virtual ~forum_tag() = default;
/**
* @brief Set name of this forum_tag object
*
* @param name Name to set
* @return Reference to self, so these method calls may be chained
*
* @note name will be truncated to 20 chars, if longer
*/
forum_tag& set_name(const std::string& name);
};
/**
* @brief A definition of a discord channel.
* There are one of these for every channel type except threads. Threads are
* special snowflakes. Get it? A Discord pun. Hahaha. .... I'll get my coat.
*/
class DPP_EXPORT channel : public managed, public json_interface<channel> {
protected:
friend struct json_interface<channel>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
channel& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build json for this channel object
*
* @param with_id include the ID in the json
* @return json JSON object
*/
virtual json to_json_impl(bool with_id = false) const;
static constexpr uint16_t CHANNEL_TYPE_MASK = 0b0000000000001111;
public:
/**
* @brief Channel name (1-100 characters).
*/
std::string name;
/**
* @brief Channel topic (0-4096 characters for forum and media channels, 0-1024 characters for all others).
*/
std::string topic;
/**
* @brief Voice region if set for voice channel, otherwise empty string.
*/
std::string rtc_region;
/**
* @brief DM recipients.
*/
std::vector<snowflake> recipients;
/**
* @brief Permission overwrites to apply to base permissions.
*/
std::vector<permission_overwrite> permission_overwrites;
/**
* @brief A set of tags that can be used in a forum or media channel.
*/
std::vector<forum_tag> available_tags;
/**
* @brief The emoji to show as the default reaction button on a thread in a forum or media channel.
* Contains either nothing, the id of a guild's custom emoji or the unicode character of the emoji.
*/
std::variant<std::monostate, snowflake, std::string> default_reaction;
/**
* @brief Channel icon (for group DMs).
*/
utility::iconhash icon;
/**
* @brief User ID of the creator for group DMs or threads.
*/
snowflake owner_id;
/**
* @brief Parent ID (for guild channels: id of the parent category, for threads: id of the text channel this thread was created).
*/
snowflake parent_id;
/**
* @brief Guild id of the guild that owns the channel.
*/
snowflake guild_id;
/**
* @brief ID of last message to be sent to the channel.
*
* @warning may not point to an existing or valid message/thread.
*/
snowflake last_message_id;
/**
* @brief Timestamp of last pinned message.
*/
time_t last_pin_timestamp;
/**
* @brief This is only filled when the channel is part of the `resolved` set
* sent within an interaction. Any other time it contains zero. When filled,
* it contains the calculated permission bitmask of the user issuing the command
* within this channel.
*/
permission permissions;
/**
* @brief Sorting position, lower number means higher up the list
*/
uint16_t position;
/**
* @brief The bitrate (in kilobits) of the voice channel.
*/
uint16_t bitrate;
/**
* @brief Amount of seconds a user has to wait before sending another message (0-21600).
* Bots, as well as users with the permission manage_messages or manage_channel, are unaffected
*/
uint16_t rate_limit_per_user;
/**
* @brief The initial `rate_limit_per_user` to set on newly created threads in a channel.
* This field is copied to the thread at creation time and does not live update.
*/
uint16_t default_thread_rate_limit_per_user;
/**
* @brief Default duration, copied onto newly created threads. Used by the clients, not the API.
* Threads will stop showing in the channel list after the specified period of inactivity. Defaults to dpp::arc_1_day.
*/
auto_archive_duration_t default_auto_archive_duration;
/**
* @brief The default sort order type used to order posts in forum and media channels.
*/
default_forum_sort_order_t default_sort_order;
/**
* @brief Flags bitmap (dpp::channel_flags)
*/
uint16_t flags;
/**
* @brief Maximum user limit for voice channels (0-99)
*/
uint8_t user_limit;
/** Constructor */
channel();
/** Destructor */
virtual ~channel();
/**
* @brief Create a mentionable channel.
* @param id The ID of the channel.
* @return std::string The formatted mention of the channel.
*/
static std::string get_mention(const snowflake& id);
/**
* @brief Set name of this channel object
*
* @param name Name to set
* @return Reference to self, so these method calls may be chained
*
* @note name will be truncated to 100 chars, if longer
* @throw dpp::length_exception if length < 1
*/
channel& set_name(const std::string& name);
/**
* @brief Set topic of this channel object
*
* @param topic Topic to set
* @return Reference to self, so these method calls may be chained
*
* @note topic will be truncated to 1024 chars, if longer
*/
channel& set_topic(const std::string& topic);
/**
* @brief Set type of this channel object
*
* @param type Channel type to set
* @return Reference to self, so these method calls may be chained
*/
channel& set_type(channel_type type);
/**
* @brief Set the default forum layout type for the forum channel
*
* @param layout_type The layout type
* @return Reference to self, so these method calls may be chained
*/
channel& set_default_forum_layout(forum_layout_type layout_type);
/**
* @brief Set the default forum sort order for the forum channel
*
* @param sort_order The sort order
* @return Reference to self, so these method calls may be chained
*/
channel& set_default_sort_order(default_forum_sort_order_t sort_order);
/**
* @brief Set flags for this channel object
*
* @param flags Flag bitmask to set from dpp::channel_flags
* @return Reference to self, so these method calls may be chained
*/
channel& set_flags(const uint16_t flags);
/**
* @brief Add (bitwise OR) a flag to this channel object
*
* @param flag Flag bit to add from dpp::channel_flags
* @return Reference to self, so these method calls may be chained
*/
channel& add_flag(const channel_flags flag);
/**
* @brief Remove (bitwise NOT AND) a flag from this channel object
*
* @param flag Flag bit to remove from dpp::channel_flags
* @return Reference to self, so these method calls may be chained
*/
channel& remove_flag(const channel_flags flag);
/**
* @brief Set position of this channel object
*
* @param position Position to set
* @return Reference to self, so these method calls may be chained
*/
channel& set_position(const uint16_t position);
/**
* @brief Set guild_id of this channel object
*
* @param guild_id Guild ID to set
* @return Reference to self, so these method calls may be chained
*/
channel& set_guild_id(const snowflake guild_id);
/**
* @brief Set parent_id of this channel object
*
* @param parent_id Parent ID to set
* @return Reference to self, so these method calls may be chained
*/
channel& set_parent_id(const snowflake parent_id);
/**
* @brief Set user_limit of this channel object
*
* @param user_limit Limit to set
* @return Reference to self, so these method calls may be chained
*/
channel& set_user_limit(const uint8_t user_limit);
/**
* @brief Set bitrate of this channel object
*
* @param bitrate Bitrate to set (in kilobits)
* @return Reference to self, so these method calls may be chained
*/
channel& set_bitrate(const uint16_t bitrate);
/**
* @brief Set nsfw property of this channel object
*
* @param is_nsfw true, if channel is nsfw
* @return Reference to self, so these method calls may be chained
*/
channel& set_nsfw(const bool is_nsfw);
/**
* @brief Set lock permissions property of this channel object
* Used only with the reorder channels method
*
* @param is_lock_permissions true, if we are to inherit permissions from the category
* @return Reference to self, so these method calls may be chained
*/
channel& set_lock_permissions(const bool is_lock_permissions);
/**
* @brief Set rate_limit_per_user of this channel object
*
* @param rate_limit_per_user rate_limit_per_user (slowmode in sec) to set
* @return Reference to self, so these method calls may be chained
*/
channel& set_rate_limit_per_user(const uint16_t rate_limit_per_user);
/**
* @brief Add permission overwrites for a user or role.
* If the channel already has permission overwrites for the passed target, the existing ones will be adjusted by the passed permissions
*
* @param target ID of the role or the member you want to adjust overwrites for
* @param type type of overwrite
* @param allowed_permissions bitmask of dpp::permissions you want to allow for this user/role in this channel. Note: You can use the dpp::permission class
* @param denied_permissions bitmask of dpp::permissions you want to deny for this user/role in this channel. Note: You can use the dpp::permission class
*
* **Example:**
*
* ```cpp
* channel.add_permission_overwrite(388499352297406481, dpp::ot_role, dpp::p_manage_channels | dpp::p_manage_messages, 0);
* // Allows p_manage_channels and p_manage_messages permissions for the provided role.
* ```
*
* @return Reference to self, so these method calls may be chained
*/
channel& add_permission_overwrite(const snowflake target, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions);
/**
* @brief Set permission overwrites for a user or role on this channel object. Old permission overwrites for the target will be overwritten
*
* @param target ID of the role or the member you want to set overwrites for
* @param type type of overwrite
* @param allowed_permissions bitmask of allowed dpp::permissions for this user/role in this channel. Note: You can use the dpp::permission class
* @param denied_permissions bitmask of denied dpp::permissions for this user/role in this channel. Note: You can use the dpp::permission class
*
* **Example:**
*
* ```cpp
* channel.set_permission_overwrite(388499352297406481, dpp::ot_role, dpp::p_manage_channels | dpp::p_manage_messages, 0);
* // Sets the allowed permissions to p_manage_channels and p_manage_messages and removes all denied permission flags for the provided role.
* ```
*
* @return Reference to self, so these method calls may be chained
*
* @note If both `allowed_permissions` and `denied_permissions` parameters are 0, the permission overwrite for the target will be removed
*/
channel& set_permission_overwrite(const snowflake target, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions);
/**
* @brief Remove channel specific permission overwrites of a user or role
*
* @param target ID of the role or the member you want to remove permission overwrites of
* @param type type of overwrite
*
* @return Reference to self, so these method calls may be chained
*/
channel& remove_permission_overwrite(const snowflake target, const overwrite_type type);
/**
* @brief Get the channel type
*
* @return channel_type Channel type
*/
channel_type get_type() const;
/**
* @brief Get the default forum layout type used to display posts in forum channels
*
* @return forum_layout_types Forum layout type
*/
forum_layout_type get_default_forum_layout() const;
/**
* @brief Get the mention ping for the channel
*
* @return std::string mention
*/
std::string get_mention() const;
/**
* @brief Get the overall permissions for a member in this channel, including channel overwrites, role permissions and admin privileges.
*
* @param user The user to resolve the permissions for
* @return permission Permission overwrites for the member. Made of bits in dpp::permissions.
* @note Requires role cache to be enabled (it's enabled by default).
*
* @note This is an alias for guild::permission_overwrites and searches for the guild in the cache,
* so consider using guild::permission_overwrites if you already have the guild object.
*
* @warning The method will search for the guild member in the cache by the users id.
* If the guild member is not in cache, the method will always return 0.
*/
permission get_user_permissions(const class user* user) const;
/**
* @brief Get the overall permissions for a member in this channel, including channel overwrites, role permissions and admin privileges.
*
* @param member The member to resolve the permissions for
* @return permission Permission overwrites for the member. Made of bits in dpp::permissions.
* @note Requires role cache to be enabled (it's enabled by default).
*
* @note This is an alias for guild::permission_overwrites and searches for the guild in the cache,
* so consider using guild::permission_overwrites if you already have the guild object.
*/
permission get_user_permissions(const class guild_member &member) const;
/**
* @brief Return a map of members on the channel, built from the guild's
* member list based on which members have the VIEW_CHANNEL permission.
* Does not return reliable information for voice channels, use
* dpp::channel::get_voice_members() instead for this.
* @return A map of guild members keyed by user id.
* @note If the guild this channel belongs to is not in the cache, the function will always return 0.
*/
std::map<snowflake, class guild_member*> get_members();
/**
* @brief Get a map of members in this channel, if it is a voice channel.
* The map is keyed by snowflake id of the user.
*
* @return std::map<snowflake, voicestate> The voice members of the channel
*/
std::map<snowflake, voicestate> get_voice_members();
/**
* @brief Get the channel's icon url (if its a group DM), otherwise returns an empty string
*
* @param size The size of the icon in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized icon is returned.
* @param format The format to use for the avatar. It can be one of `i_webp`, `i_jpg` or `i_png`.
* @return std::string icon url or an empty string, if required attributes are missing or an invalid format was passed
*/
std::string get_icon_url(uint16_t size = 0, const image_type format = i_png) const;
/**
* @brief Returns string of URL to channel
*
* @return string of URL to channel
*/
std::string get_url() const;
/**
* @brief Returns true if the channel is NSFW gated
*
* @return true if NSFW
*/
bool is_nsfw() const;
/**
* @brief Returns true if the permissions are to be synced with the category it is in.
* Used only and set manually when using the reorder channels method.
*
* @return true if keeping permissions
*/
bool is_locked_permissions() const;
/**
* @brief Returns true if the channel is a text channel
*
* @return true if text channel
*/
bool is_text_channel() const;
/**
* @brief Returns true if the channel is a DM
*
* @return true if is a DM
*/
bool is_dm() const;
/**
* @brief Returns true if the channel is a voice channel
*
* @return true if voice channel
*/
bool is_voice_channel() const;
/**
* @brief Returns true if the channel is a group DM channel
*
* @return true if group DM
*/
bool is_group_dm() const;
/**
* @brief Returns true if the channel is a category
*
* @return true if a category
*/
bool is_category() const;
/**
* @brief Returns true if the channel is a forum
*
* @return true if a forum
*/
bool is_forum() const;
/**
* @brief Returns true if the channel is a media channel
*
* @return true if media channel
*/
bool is_media_channel() const;
/**
* @brief Returns true if the channel is an announcement channel
*
* @return true if announcement channel
*/
bool is_news_channel() const;
/**
* @brief Returns true if the channel is a store channel
* @deprecated store channels are deprecated by Discord
*
* @return true if store channel
*/
bool is_store_channel() const;
/**
* @brief Returns true if the channel is a stage channel
*
* @return true if stage channel
*/
bool is_stage_channel() const;
/**
* @brief Returns true if video quality is auto
*
* @return true if video quality is auto
*/
bool is_video_auto() const;
/**
* @brief Returns true if video quality is 720p
*
* @return true if video quality is 720p
*/
bool is_video_720p() const;
/**
* @brief Returns true if channel is a pinned thread in forum
*
* @return true, if channel is a pinned thread in forum
*/
bool is_pinned_thread() const;
/**
* @brief Returns true if a tag is required to be specified when creating a thread in a forum channel
*
* @return true, if a tag is required to be specified when creating a thread in a forum channel
*/
bool is_tag_required() const;
/**
* @brief Returns true if embedded media download options are hidden in a media channel
*
* @return true, if embedded media download options are hidden in a media channel
*/
bool is_download_options_hidden() const;
};
/**
* @brief Serialize a permission_overwrite object to json
*
* @param j JSON object to serialize to
* @param po object to serialize
*/
void to_json(nlohmann::json& j, const permission_overwrite& po);
/**
* @brief A group of channels
*/
typedef std::unordered_map<snowflake, channel> channel_map;
}

4109
include/dpp/cluster.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

473
include/dpp/collector.h Normal file
View File

@ -0,0 +1,473 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/cluster.h>
#include <dpp/timed_listener.h>
#include <time.h>
#include <vector>
#include <functional>
#include <string>
namespace dpp {
/**
* @brief Collects objects from events during a specified time period.
*
* This template must be specialised. There are premade specialisations which you can use
* such as dpp::reaction_collector and dpp::message_collector. For these specialised instances
* all you need to do is derive a simple class from them which implements collector::completed().
*
* A collector will run for the specified number of seconds, attaching itself to the
* given event. During this time any events pass through the collector and collector::filter().
* This function can return a pointer to an object to allow a copy of that object to be stored
* to a vector, or it can return nullptr to do nothing with that object. For example a collector
* attached to on_message_create would receive an event with the type message_create_t, and from
* this may decide to extract the message_create_t::msg structure, returning a pointer to it, or
* instead may choose to return a nullptr.
*
* When either the predetermined timeout is reached, or the collector::cancel() method is called,
* or the collector is destroyed, the collector::completed() method is called, which will be
* passed a list of collected objects in the order they were collected.
*
* @tparam T parameter type of the event this collector will monitor
* @tparam C object type this collector will store
*/
template<class T, class C> class collector
{
protected:
/**
* @brief Owning cluster.
*/
class cluster* owner;
private:
/**
* @brief Timed listener.
*/
timed_listener<event_router_t<T>, std::function<void(const T&)>>* tl;
/**
* @brief Stored list.
*/
std::vector<C> stored;
/**
* @brief Trigger flag.
*/
bool triggered;
public:
/**
* @brief Construct a new collector object.
*
* The timer for the collector begins immediately on construction of the object.
*
* @param cl Pointer to cluster which manages this collector
* @param duration Duration in seconds to run the collector for
* @param event Event to attach to, e.g. cluster::on_message_create
*/
collector(class cluster* cl, uint64_t duration, event_router_t<T> & event) : owner(cl), triggered(false) {
std::function<void(const T&)> f = [this](const T& event) {
const C* v = filter(event);
if (v) {
stored.push_back(*v);
}
};
tl = new dpp::timed_listener<event_router_t<T>, std::function<void(const T&)>>(cl, duration, event, f, [this]([[maybe_unused]] dpp::timer timer_handle) {
if (!triggered) {
triggered = true;
completed(stored);
}
});
}
/**
* @brief You must implement this function to receive the completed list of
* captured objects.
* @param list The list of captured objects in captured order
*/
virtual void completed(const std::vector<C>& list) = 0;
/**
* @brief Filter the list of elements.
*
* Every time an event is fired on the collector, this method wil be called
* to determine if we should add an object to the list or not. This function
* can then process the `element` value, extract the parts which are to be
* saved to a list (e.g. a dpp::message out of a dpp::message_create_t) and
* return it as the return value. Returning a value of nullptr causes no
* object to be stored.
*
* Here is an example of how to filter messages which have specific text in them.
* This should be used with the specialised type dpp::message_collector
*
* ```cpp
* virtual const dpp::message* filter(const dpp::message_create_t& m) {
* if (m.msg.content.find("something i want") != std::string::npos) {
* return &m.msg;
* } else {
* return nullptr;
* }
* }
* ```
*
* @param element The event data to filter
* @return const C* Returned object or nullptr
*/
virtual const C* filter(const T& element) = 0;
/**
* @brief Immediately cancels the collector.
*
* Use this if you have met the conditions for which you are collecting objects
* early, e.g. you were watching for a message containing 'yes' or 'no' and have
* received it before the time is up.
*
* @note Causes calling of the completed() method if it has not yet been called.
*/
virtual void cancel() {
delete tl;
tl = nullptr;
}
/**
* @brief Destroy the collector object.
* @note Causes calling of the completed() method if it has not yet been called.
*/
virtual ~collector() {
delete tl;
}
};
/**
* @brief Represents a reaction.
* Can be filled for use in a collector
*/
class collected_reaction : public managed {
public:
/**
* @brief Reacting user.
*/
user react_user{};
/**
* @brief Reacting guild.
*/
guild react_guild{};
/**
* @brief Reacting guild member.
*/
guild_member react_member{};
/**
* @brief Reacting channel.
*/
channel react_channel{};
/**
* @brief Reacted emoji.
*/
emoji react_emoji{};
/**
* @brief Optional: ID of the user who authored the message which was reacted to.
*/
snowflake message_author_id{};
};
/**
* @brief Template type for base class of channel collector
*/
typedef dpp::collector<dpp::channel_create_t, dpp::channel> channel_collector_t;
/**
* @brief Template type for base class of thread collector
*/
typedef dpp::collector<dpp::thread_create_t, dpp::thread> thread_collector_t;
/**
* @brief Template type for base class of role collector
*/
typedef dpp::collector<dpp::guild_role_create_t, dpp::role> role_collector_t;
/**
* @brief Template type for base class of scheduled event collector
*/
typedef dpp::collector<dpp::guild_scheduled_event_create_t, dpp::scheduled_event> scheduled_event_collector_t;
/**
* @brief Template type for base class of message collector
*/
typedef dpp::collector<dpp::message_create_t, dpp::message> message_collector_t;
/**
* @brief Template type for base class of message reaction collector
*/
typedef dpp::collector<dpp::message_reaction_add_t, dpp::collected_reaction> reaction_collector_t;
/**
* @brief Message collector.
* Collects messages during a set timeframe and returns them in a list via the completed() method.
*/
class message_collector : public message_collector_t {
public:
/**
* @brief Construct a new message collector object
*
* @param cl cluster to associate the collector with
* @param duration Duration of time to run the collector for in seconds
*/
message_collector(cluster* cl, uint64_t duration) : message_collector_t::collector(cl, duration, cl->on_message_create) { }
/**
* @brief Return the completed collection
*
* @param list items collected during the timeframe specified
*/
virtual void completed(const std::vector<dpp::message>& list) = 0;
/**
* @brief Select and filter the items which are to appear in the list
* This is called every time a new event is fired, to filter the event and determine which
* of the items is sent to the list. Returning nullptr excludes the item from the list.
*
* @param element element to filter
* @return Returned item to add to the list, or nullptr to skip adding this element
*/
virtual const dpp::message* filter(const dpp::message_create_t& element) { return &element.msg; }
/**
* @brief Destroy the message collector object
*/
virtual ~message_collector() = default;
};
/**
* @brief Reaction collector.
* Collects message reactions during a set timeframe and returns them in a list via the completed() method.
*/
class reaction_collector : public reaction_collector_t {
/**
* @brief The ID of the message.
*/
snowflake message_id;
/**
* @brief The reaction.
*/
collected_reaction react;
public:
/**
* @brief Construct a new reaction collector object
*
* @param cl cluster to associate the collector with
* @param duration Duration of time to run the collector for in seconds
* @param msg_id Optional message ID. If specified, only collects reactions for the given message
*/
reaction_collector(cluster* cl, uint64_t duration, snowflake msg_id = 0) : reaction_collector_t::collector(cl, duration, cl->on_message_reaction_add), message_id(msg_id) { }
/**
* @brief Return the completed collection
*
* @param list items collected during the timeframe specified
*/
virtual void completed(const std::vector<dpp::collected_reaction>& list) = 0;
/**
* @brief Select and filter the items which are to appear in the list
* This is called every time a new event is fired, to filter the event and determine which
* of the items is sent to the list. Returning nullptr excludes the item from the list.
*
* @param element element to filter
* @return Returned item to add to the list, or nullptr to skip adding this element
*/
virtual const dpp::collected_reaction* filter(const dpp::message_reaction_add_t& element) {
/* Capture reactions for given message ID only */
if (message_id.empty() || element.message_id == message_id) {
react.id = element.message_id;
react.react_user = element.reacting_user;
react.react_guild = element.reacting_guild;
react.react_member = element.reacting_member;
react.react_channel = element.reacting_channel;
react.react_emoji = element.reacting_emoji;
react.message_author_id = element.message_author_id;
return &react;
} else {
return nullptr;
}
}
/**
* @brief Destroy the reaction collector object
*/
virtual ~reaction_collector() = default;
};
/**
* @brief Channel collector.
* Collects channels during a set timeframe and returns them in a list via the completed() method.
*/
class channel_collector : public channel_collector_t {
public:
/**
* @brief Construct a new channel collector object
*
* @param cl cluster to associate the collector with
* @param duration Duration of time to run the collector for in seconds
*/
channel_collector(cluster* cl, uint64_t duration) : channel_collector_t::collector(cl, duration, cl->on_channel_create) { }
/**
* @brief Return the completed collection
*
* @param list items collected during the timeframe specified
*/
virtual void completed(const std::vector<dpp::channel>& list) = 0;
/**
* @brief Select and filter the items which are to appear in the list
* This is called every time a new event is fired, to filter the event and determine which
* of the items is sent to the list. Returning nullptr excludes the item from the list.
*
* @param element element to filter
* @return Returned item to add to the list, or nullptr to skip adding this element
*/
virtual const dpp::channel* filter(const dpp::channel_create_t& element) { return &element.created; }
/**
* @brief Destroy the channel collector object
*/
virtual ~channel_collector() = default;
};
/**
* @brief Thread collector.
* Collects threads during a set timeframe and returns them in a list via the completed() method.
*/
class thread_collector : public thread_collector_t {
public:
/**
* @brief Construct a new thread collector object
*
* @param cl cluster to associate the collector with
* @param duration Duration of time to run the collector for in seconds
*/
thread_collector(cluster* cl, uint64_t duration) : thread_collector_t::collector(cl, duration, cl->on_thread_create) { }
/**
* @brief Return the completed collection
*
* @param list items collected during the timeframe specified
*/
virtual void completed(const std::vector<dpp::thread>& list) = 0;
/**
* @brief Select and filter the items which are to appear in the list
* This is called every time a new event is fired, to filter the event and determine which
* of the items is sent to the list. Returning nullptr excludes the item from the list.
*
* @param element element to filter
* @return Returned item to add to the list, or nullptr to skip adding this element
*/
virtual const dpp::thread* filter(const dpp::thread_create_t& element) { return &element.created; }
/**
* @brief Destroy the thread collector object
*/
virtual ~thread_collector() = default;
};
/**
* @brief Role collector.
* Collects guild roles during a set timeframe and returns them in a list via the completed() method.
*/
class role_collector : public role_collector_t {
public:
/**
* @brief Construct a new role collector object
*
* @param cl cluster to associate the collector with
* @param duration Duration of time to run the collector for in seconds
*/
role_collector(cluster* cl, uint64_t duration) : role_collector_t::collector(cl, duration, cl->on_guild_role_create) { }
/**
* @brief Return the completed collection
*
* @param list items collected during the timeframe specified
*/
virtual void completed(const std::vector<dpp::role>& list) = 0;
/**
* @brief Select and filter the items which are to appear in the list
* This is called every time a new event is fired, to filter the event and determine which
* of the items is sent to the list. Returning nullptr excludes the item from the list.
*
* @param element element to filter
* @return Returned item to add to the list, or nullptr to skip adding this element
*/
virtual const dpp::role* filter(const dpp::guild_role_create_t& element) { return &element.created; }
/**
* @brief Destroy the role collector object
*/
virtual ~role_collector() = default;
};
/**
* @brief Scheduled event collector.
* Collects messages during a set timeframe and returns them in a list via the completed() method.
*/
class scheduled_event_collector : public scheduled_event_collector_t {
public:
/**
* @brief Construct a new scheduled event collector object
*
* @param cl cluster to associate the collector with
* @param duration Duration of time to run the collector for in seconds
*/
scheduled_event_collector(cluster* cl, uint64_t duration) : scheduled_event_collector_t::collector(cl, duration, cl->on_guild_scheduled_event_create) { }
/**
* @brief Return the completed collection
*
* @param list items collected during the timeframe specified
*/
virtual void completed(const std::vector<dpp::scheduled_event>& list) = 0;
/**
* @brief Select and filter the items which are to appear in the list
* This is called every time a new event is fired, to filter the event and determine which
* of the items is sent to the list. Returning nullptr excludes the item from the list.
*
* @param element element to filter
* @return Returned item to add to the list, or nullptr to skip adding this element
*/
virtual const dpp::scheduled_event* filter(const dpp::guild_scheduled_event_create_t& element) { return &element.created; }
/**
* @brief Destroy the scheduled event collector object
*/
virtual ~scheduled_event_collector() = default;
};
}

745
include/dpp/colors.h Normal file
View File

@ -0,0 +1,745 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <cstdint>
/**
* @brief The main namespace for D++ functions. classes and types
*/
namespace dpp {
/**
* @brief predefined color constants.
*/
namespace colors {
const uint32_t
white = 0xFFFFFF,
discord_white = 0xFFFFFE,
light_gray = 0xC0C0C0,
gray = 0x808080,
dark_gray = 0x404040,
black = 0x000000,
discord_black = 0x000001,
red = 0xFF0000,
pink = 0xFFAFAF,
orange = 0xFFC800,
yellow = 0xFFFF00,
green = 0x00FF00,
magenta = 0xFF00FF,
cyan = 0x00FFFF,
blue = 0x0000FF,
light_sea_green = 0x1ABC9C,
medium_sea_green = 0x2ECC71,
summer_sky = 0x3498DB,
deep_lilac = 0x9B59B6,
ruby = 0xE91E63,
moon_yellow = 0xF1C40F,
tahiti_gold = 0xE67E22,
cinnabar = 0xE74C3C,
submarine = 0x95A5A6,
blue_aquamarine = 0x607D8B,
deep_sea = 0x11806A,
sea_green = 0x1F8B4C,
endeavour = 0x206694,
vivid_violet = 0x71368A,
jazzberry_jam = 0xAD1457,
dark_goldenrod = 0xC27C0E,
rust = 0xA84300,
brown = 0x992D22,
gray_chateau = 0x979C9F,
bismark = 0x546E7A,
sti_blue = 0x0E4BEF,
wrx_blue = 0x00247D,
rallyart_crimson = 0xE60012,
lime = 0x00FF00,
forest_green = 0x228B22,
cadmium_green = 0x097969,
aquamarine = 0x7FFFD4,
blue_green = 0x088F8F,
raspberry = 0xE30B5C,
scarlet_red = 0xFF2400,
night = 0x0C090A,
charcoal = 0x34282C,
oil = 0x3B3131,
light_black = 0x454545,
black_cat = 0x413839,
iridium = 0x3D3C3A,
black_eel = 0x463E3F,
black_cow = 0x4C4646,
gray_wolf = 0x504A4B,
grey_wolf = 0x504A4B,
vampire_gray = 0x565051,
vampire_grey = 0x565051,
iron_gray = 0x52595D,
iron_grey = 0x52595D,
gray_dolphin = 0x5C5858,
grey_dolphin = 0x5C5858,
carbon_gray = 0x625D5D,
carbon_grey = 0x625D5D,
ash_gray = 0x666362,
ash_grey = 0x666362,
dim_gray = 0x696969,
dim_grey = 0x696969,
nardo_gray = 0x686A6C,
nardo_grey = 0x686A6C,
cloudy_gray = 0x6D6968,
cloudy_grey = 0x6D6968,
smokey_gray = 0x726E6D,
smokey_grey = 0x726E6D,
alien_gray = 0x736F6E,
alien_grey = 0x736F6E,
sonic_silver = 0x757575,
platinum_gray = 0x797979,
platinum_grey = 0x797979,
granite = 0x837E7C,
battleship_gray = 0x848482,
battleship_grey = 0x848482,
gunmetal_gray = 0x8D918D,
gunmetal_grey = 0x8D918D,
gray_cloud = 0xB6B6B4,
grey_cloud = 0xB6B6B4,
silver = 0xC0C0C0,
pale_silver = 0xC9C0BB,
gray_goose = 0xD1D0CE,
grey_goose = 0xD1D0CE,
platinum_silver = 0xCECECE,
silver_white = 0xDADBDD,
gainsboro = 0xDCDCDC,
platinum = 0xE5E4E2,
metallic_silver = 0xBCC6CC,
blue_gray = 0x98AFC7,
blue_grey = 0x98AFC7,
roman_silver = 0x838996,
light_slate_gray = 0x778899,
light_slate_grey = 0x778899,
slate_gray = 0x708090,
slate_grey = 0x708090,
rat_gray = 0x6D7B8D,
slate_granite_gray = 0x657383,
slate_granite_grey = 0x657383,
jet_gray = 0x616D7E,
jet_grey = 0x616D7E,
mist_blue = 0x646D7E,
marble_blue = 0x566D7E,
slate_blue_grey = 0x737CA1,
slate_blue_gray = 0x737CA1,
light_purple_blue = 0x728FCE,
azure_blue = 0x4863A0,
blue_jay = 0x2B547E,
charcoal_blue = 0x36454F,
dark_blue_grey = 0x29465B,
dark_slate = 0x2B3856,
deep_sea_blue = 0x123456,
night_blue = 0x151B54,
midnight_blue = 0x191970,
navy = 0x000080,
denim_dark_blue = 0x151B8D,
dark_blue = 0x00008B,
lapis_blue = 0x15317E,
new_midnight_blue = 0x0000A0,
earth_blue = 0x0000A5,
cobalt_blue = 0x0020C2,
medium_blue = 0x0000CD,
blueberry_blue = 0x0041C2,
canary_blue = 0x2916F5,
samco_blue = 0x0002FF,
bright_blue = 0x0909FF,
blue_orchid = 0x1F45FC,
sapphire_blue = 0x2554C7,
blue_eyes = 0x1569C7,
bright_navy_blue = 0x1974D2,
balloon_blue = 0x2B60DE,
royal_blue = 0x4169E1,
ocean_blue = 0x2B65EC,
blue_ribbon = 0x306EFF,
blue_dress = 0x157DEC,
neon_blue = 0x1589FF,
dodger_blue = 0x1E90FF,
glacial_blue_ice = 0x368BC1,
steel_blue = 0x4682B4,
silk_blue = 0x488AC7,
windows_blue = 0x357EC7,
blue_ivy = 0x3090C7,
blue_koi = 0x659EC7,
columbia_blue = 0x87AFC7,
baby_blue = 0x95B9C7,
cornflower_blue = 0x6495ED,
sky_blue_dress = 0x6698FF,
iceberg = 0x56A5EC,
butterfly_blue = 0x38ACEC,
deep_sky_blue = 0x00BFFF,
midday_blue = 0x3BB9FF,
crystal_blue = 0x5CB3FF,
denim_blue = 0x79BAEC,
day_sky_blue = 0x82CAFF,
light_sky_blue = 0x87CEFA,
sky_blue = 0x87CEEB,
jeans_blue = 0xA0CFEC,
blue_angel = 0xB7CEEC,
pastel_blue = 0xB4CFEC,
light_day_blue = 0xADDFFF,
sea_blue = 0xC2DFFF,
heavenly_blue = 0xC6DEFF,
robin_egg_blue = 0xBDEDFF,
powder_blue = 0xB0E0E6,
coral_blue = 0xAFDCEC,
light_blue = 0xADD8E6,
light_steel_blue = 0xB0CFDE,
gulf_blue = 0xC9DFEC,
pastel_light_blue = 0xD5D6EA,
lavender_blue = 0xE3E4FA,
white_blue = 0xDBE9FA,
lavender = 0xE6E6FA,
water = 0xEBF4FA,
alice_blue = 0xF0F8FF,
ghost_white = 0xF8F8FF,
azure = 0xF0FFFF,
light_cyan = 0xE0FFFF,
light_slate = 0xCCFFFF,
electric_blue = 0x9AFEFF,
tron_blue = 0x7DFDFE,
blue_zircon = 0x57FEFF,
aqua = 0x00FFFF,
bright_cyan = 0x0AFFFF,
celeste = 0x50EBEC,
blue_diamond = 0x4EE2EC,
bright_turquoise = 0x16E2F5,
blue_lagoon = 0x8EEBEC,
pale_turquoise = 0xAFEEEE,
pale_blue_lily = 0xCFECEC,
light_teal = 0xB3D9D9,
tiffany_blue = 0x81D8D0,
blue_hosta = 0x77BFC7,
cyan_opaque = 0x92C7C7,
northern_lights_blue = 0x78C7C7,
medium_aquamarine = 0x66CDAA,
magic_mint = 0xAAF0D1,
light_aquamarine = 0x93FFE8,
bright_teal = 0x01F9C6,
turquoise = 0x40E0D0,
medium_turquoise = 0x48D1CC,
deep_turquoise = 0x48CCCD,
jellyfish = 0x46C7C7,
blue_turquoise = 0x43C6DB,
dark_turquoise = 0x00CED1,
macaw_blue_green = 0x43BFC7,
seafoam_green = 0x3EA99F,
cadet_blue = 0x5F9EA0,
blue_chill = 0x3B9C9C,
dark_cyan = 0x008B8B,
teal_green = 0x00827F,
teal = 0x008080,
teal_blue = 0x007C80,
medium_teal = 0x045F5F,
dark_teal = 0x045D5D,
deep_teal = 0x033E3E,
dark_slate_gray = 0x25383C,
dark_slate_grey = 0x25383C,
gunmetal = 0x2C3539,
blue_moss_green = 0x3C565B,
beetle_green = 0x4C787E,
grayish_turquoise = 0x5E7D7E,
greenish_blue = 0x307D7E,
aquamarine_stone = 0x348781,
sea_turtle_green = 0x438D80,
dull_sea_green = 0x4E8975,
dark_green_blue = 0x1F6357,
deep_sea_green = 0x306754,
bottle_green = 0x006A4E,
elf_green = 0x1B8A6B,
dark_mint = 0x31906E,
jade = 0x00A36C,
earth_green = 0x34A56F,
chrome_green = 0x1AA260,
emerald = 0x50C878,
mint = 0x3EB489,
metallic_green = 0x7C9D8E,
camouflage_green = 0x78866B,
sage_green = 0x848B79,
hazel_green = 0x617C58,
venom_green = 0x728C00,
olive_drab = 0x6B8E23,
olive = 0x808000,
dark_olive_green = 0x556B2F,
military_green = 0x4E5B31,
green_leaves = 0x3A5F0B,
army_green = 0x4B5320,
fern_green = 0x667C26,
fall_forest_green = 0x4E9258,
irish_green = 0x08A04B,
pine_green = 0x387C44,
medium_forest_green = 0x347235,
jungle_green = 0x347C2C,
cactus_green = 0x227442,
dark_green = 0x006400,
deep_green = 0x056608,
deep_emerald_green = 0x046307,
hunter_green = 0x355E3B,
dark_forest_green = 0x254117,
lotus_green = 0x004225,
seaweed_green = 0x437C17,
shamrock_green = 0x347C17,
green_onion = 0x6AA121,
moss_green = 0x8A9A5B,
grass_green = 0x3F9B0B,
green_pepper = 0x4AA02C,
dark_lime_green = 0x41A317,
parrot_green = 0x12AD2B,
clover_green = 0x3EA055,
dinosaur_green = 0x73A16C,
green_snake = 0x6CBB3C,
alien_green = 0x6CC417,
green_apple = 0x4CC417,
lime_green = 0x32CD32,
pea_green = 0x52D017,
kelly_green = 0x4CC552,
zombie_green = 0x54C571,
green_peas = 0x89C35C,
dollar_bill_green = 0x85BB65,
frog_green = 0x99C68E,
turquoise_green = 0xA0D6B4,
dark_sea_green = 0x8FBC8F,
basil_green = 0x829F82,
gray_green = 0xA2AD9C,
iguana_green = 0x9CB071,
citron_green = 0x8FB31D,
acid_green = 0xB0BF1A,
avocado_green = 0xB2C248,
pistachio_green = 0x9DC209,
salad_green = 0xA1C935,
yellow_green = 0x9ACD32,
pastel_green = 0x77DD77,
hummingbird_green = 0x7FE817,
nebula_green = 0x59E817,
stoplight_go_green = 0x57E964,
neon_green = 0x16F529,
jade_green = 0x5EFB6E,
lime_mint_green = 0x36F57F,
spring_green = 0x00FF7F,
medium_spring_green = 0x00FA9A,
emerald_green = 0x5FFB17,
lawn_green = 0x7CFC00,
bright_green = 0x66FF00,
chartreuse = 0x7FFF00,
yellow_lawn_green = 0x87F717,
aloe_vera_green = 0x98F516,
dull_green_yellow = 0xB1FB17,
lemon_green = 0xADF802,
green_yellow = 0xADFF2F,
chameleon_green = 0xBDF516,
neon_yellow_green = 0xDAEE01,
yellow_green_grosbeak = 0xE2F516,
tea_green = 0xCCFB5D,
slime_green = 0xBCE954,
algae_green = 0x64E986,
light_green = 0x90EE90,
dragon_green = 0x6AFB92,
pale_green = 0x98FB98,
mint_green = 0x98FF98,
green_thumb = 0xB5EAAA,
organic_brown = 0xE3F9A6,
light_jade = 0xC3FDB8,
light_mint_green = 0xC2E5D3,
light_rose_green = 0xDBF9DB,
chrome_white = 0xE8F1D4,
honeydew = 0xF0FFF0,
mint_cream = 0xF5FFFA,
lemon_chiffon = 0xFFFACD,
parchment = 0xFFFFC2,
cream = 0xFFFFCC,
cream_white = 0xFFFDD0,
light_goldenrod_yellow = 0xFAFAD2,
light_yellow = 0xFFFFE0,
beige = 0xF5F5DC,
cornsilk = 0xFFF8DC,
blonde = 0xFBF6D9,
champagne = 0xF7E7CE,
antique_white = 0xFAEBD7,
papaya_whip = 0xFFEFD5,
blanched_almond = 0xFFEBCD,
bisque = 0xFFE4C4,
wheat = 0xF5DEB3,
moccasin = 0xFFE4B5,
peach = 0xFFE5B4,
light_orange = 0xFED8B1,
peach_puff = 0xFFDAB9,
coral_peach = 0xFBD5AB,
navajo_white = 0xFFDEAD,
golden_blonde = 0xFBE7A1,
golden_silk = 0xF3E3C3,
dark_blonde = 0xF0E2B6,
light_gold = 0xF1E5AC,
vanilla = 0xF3E5AB,
tan_brown = 0xECE5B6,
dirty_white = 0xE8E4C9,
pale_goldenrod = 0xEEE8AA,
khaki = 0xF0E68C,
cardboard_brown = 0xEDDA74,
harvest_gold = 0xEDE275,
sun_yellow = 0xFFE87C,
corn_yellow = 0xFFF380,
pastel_yellow = 0xFAF884,
neon_yellow = 0xFFFF33,
canary_yellow = 0xFFEF00,
banana_yellow = 0xF5E216,
mustard_yellow = 0xFFDB58,
golden_yellow = 0xFFDF00,
bold_yellow = 0xF9DB24,
rubber_ducky_yellow = 0xFFD801,
gold = 0xFFD700,
bright_gold = 0xFDD017,
chrome_gold = 0xFFCE44,
golden_brown = 0xEAC117,
deep_yellow = 0xF6BE00,
macaroni_and_cheese = 0xF2BB66,
saffron = 0xFBB917,
neon_gold = 0xFDBD01,
beer = 0xFBB117,
yellow_orange = 0xFFAE42,
orange_yellow = 0xFFAE42,
cantaloupe = 0xFFA62F,
cheese_orange = 0xFFA600,
brown_sand = 0xEE9A4D,
sandy_brown = 0xF4A460,
brown_sugar = 0xE2A76F,
camel_brown = 0xC19A6B,
deer_brown = 0xE6BF83,
burly_wood = 0xDEB887,
tan = 0xD2B48C,
light_french_beige = 0xC8AD7F,
sand = 0xC2B280,
sage = 0xBCB88A,
fall_leaf_brown = 0xC8B560,
ginger_brown = 0xC9BE62,
bronze_gold = 0xC9AE5D,
dark_khaki = 0xBDB76B,
olive_green = 0xBAB86C,
brass = 0xB5A642,
cookie_brown = 0xC7A317,
metallic_gold = 0xD4AF37,
bee_yellow = 0xE9AB17,
school_bus_yellow = 0xE8A317,
goldenrod = 0xDAA520,
orange_gold = 0xD4A017,
caramel = 0xC68E17,
cinnamon = 0xC58917,
peru = 0xCD853F,
bronze = 0xCD7F32,
tiger_orange = 0xC88141,
copper = 0xB87333,
dark_gold = 0xAA6C39,
metallic_bronze = 0xA97142,
dark_almond = 0xAB784E,
wood = 0x966F33,
oak_brown = 0x806517,
antique_bronze = 0x665D1E,
hazel = 0x8E7618,
dark_yellow = 0x8B8000,
dark_moccasin = 0x827839,
khaki_green = 0x8A865D,
millennium_jade = 0x93917C,
dark_beige = 0x9F8C76,
bullet_shell = 0xAF9B60,
army_brown = 0x827B60,
sandstone = 0x786D5F,
taupe = 0x483C32,
mocha = 0x493D26,
milk_chocolate = 0x513B1C,
gray_brown = 0x3D3635,
dark_coffee = 0x3B2F2F,
old_burgundy = 0x43302E,
western_charcoal = 0x49413F,
bakers_brown = 0x5C3317,
dark_brown = 0x654321,
sepia_brown = 0x704214,
dark_bronze = 0x804A00,
coffee = 0x6F4E37,
brown_bear = 0x835C3B,
red_dirt = 0x7F5217,
sepia = 0x7F462C,
sienna = 0xA0522D,
saddle_brown = 0x8B4513,
dark_sienna = 0x8A4117,
sangria = 0x7E3817,
blood_red = 0x7E3517,
chestnut = 0x954535,
coral_brown = 0x9E4638,
chestnut_red = 0xC34A2C,
mahogany = 0xC04000,
red_gold = 0xEB5406,
red_fox = 0xC35817,
dark_bisque = 0xB86500,
light_brown = 0xB5651D,
petra_gold = 0xB76734,
copper_red = 0xCB6D51,
orange_salmon = 0xC47451,
chocolate = 0xD2691E,
sedona = 0xCC6600,
papaya_orange = 0xE56717,
halloween_orange = 0xE66C2C,
neon_orange = 0xFF6700,
bright_orange = 0xFF5F1F,
pumpkin_orange = 0xF87217,
carrot_orange = 0xF88017,
dark_orange = 0xFF8C00,
construction_cone_orange = 0xF87431,
indian_saffron = 0xFF7722,
sunrise_orange = 0xE67451,
mango_orange = 0xFF8040,
coral = 0xFF7F50,
basket_ball_orange = 0xF88158,
light_salmon_rose = 0xF9966B,
light_salmon = 0xFFA07A,
dark_salmon = 0xE9967A,
tangerine = 0xE78A61,
light_copper = 0xDA8A67,
salmon_pink = 0xFF8674,
salmon = 0xFA8072,
peach_pink = 0xF98B88,
light_coral = 0xF08080,
pastel_red = 0xF67280,
pink_coral = 0xE77471,
bean_red = 0xF75D59,
valentine_red = 0xE55451,
indian_red = 0xCD5C5C,
tomato = 0xFF6347,
shocking_orange = 0xE55B3C,
orange_red = 0xFF4500,
neon_red = 0xFD1C03,
ruby_red = 0xF62217,
ferrari_red = 0xF70D1A,
fire_engine_red = 0xF62817,
lava_red = 0xE42217,
love_red = 0xE41B17,
grapefruit = 0xDC381F,
cherry_red = 0xC24641,
chilli_pepper = 0xC11B17,
fire_brick = 0xB22222,
tomato_sauce_red = 0xB21807,
carbon_red = 0xA70D2A,
cranberry = 0x9F000F,
saffron_red = 0x931314,
crimson_red = 0x990000,
red_wine = 0x990012,
wine_red = 0x990012,
dark_red = 0x8B0000,
maroon = 0x800000,
burgundy = 0x8C001A,
vermilion = 0x7E191B,
deep_red = 0x800517,
red_blood = 0x660000,
blood_night = 0x551606,
dark_scarlet = 0x560319,
black_bean = 0x3D0C02,
chocolate_brown = 0x3F000F,
midnight = 0x2B1B17,
purple_lily = 0x550A35,
purple_maroon = 0x810541,
plum_pie = 0x7D0541,
plum_velvet = 0x7D0552,
dark_raspberry = 0x872657,
velvet_maroon = 0x7E354D,
rosy_finch = 0x7F4E52,
dull_purple = 0x7F525D,
puce = 0x7F5A58,
rose_dust = 0x997070,
pastel_brown = 0xB1907F,
rosy_pink = 0xB38481,
rosy_brown = 0xBC8F8F,
khaki_rose = 0xC5908E,
lipstick_pink = 0xC48793,
pink_brown = 0xC48189,
old_rose = 0xC08081,
dusty_pink = 0xD58A94,
pink_daisy = 0xE799A3,
rose = 0xE8ADAA,
dusty_rose = 0xC9A9A6,
silver_pink = 0xC4AEAD,
gold_pink = 0xE6C7C2,
rose_gold = 0xECC5C0,
deep_peach = 0xFFCBA4,
pastel_orange = 0xF8B88B,
desert_sand = 0xEDC9AF,
unbleached_silk = 0xFFDDCA,
pig_pink = 0xFDD7E4,
pale_pink = 0xF2D4D7,
blush = 0xFFE6E8,
misty_rose = 0xFFE4E1,
pink_bubble_gum = 0xFFDFDD,
light_rose = 0xFBCFCD,
light_red = 0xFFCCCB,
warm_pink = 0xF6C6BD,
deep_rose = 0xFBBBB9,
light_pink = 0xFFB6C1,
soft_pink = 0xFFB8BF,
donut_pink = 0xFAAFBE,
baby_pink = 0xFAAFBA,
flamingo_pink = 0xF9A7B0,
pastel_pink = 0xFEA3AA,
rose_pink = 0xE7A1B0,
pink_rose = 0xE7A1B0,
cadillac_pink = 0xE38AAE,
carnation_pink = 0xF778A1,
pastel_rose = 0xE5788F,
blush_red = 0xE56E94,
pale_violet_red = 0xDB7093,
purple_pink = 0xD16587,
tulip_pink = 0xC25A7C,
bashful_pink = 0xC25283,
dark_pink = 0xE75480,
dark_hot_pink = 0xF660AB,
hot_pink = 0xFF69B4,
watermelon_pink = 0xFC6C85,
violet_red = 0xF6358A,
hot_deep_pink = 0xF52887,
bright_pink = 0xFF007F,
deep_pink = 0xFF1493,
neon_pink = 0xF535AA,
chrome_pink = 0xFF33AA,
neon_hot_pink = 0xFD349C,
pink_cupcake = 0xE45E9D,
royal_pink = 0xE759AC,
dimorphotheca_magenta = 0xE3319D,
pink_lemonade = 0xE4287C,
red_pink = 0xFA2A55,
crimson = 0xDC143C,
bright_maroon = 0xC32148,
rose_red = 0xC21E56,
rogue_pink = 0xC12869,
burnt_pink = 0xC12267,
pink_violet = 0xCA226B,
magenta_pink = 0xCC338B,
medium_violet_red = 0xC71585,
dark_carnation_pink = 0xC12283,
raspberry_purple = 0xB3446C,
pink_plum = 0xB93B8F,
orchid = 0xDA70D6,
deep_mauve = 0xDF73D4,
violet = 0xEE82EE,
fuchsia_pink = 0xFF77FF,
bright_neon_pink = 0xF433FF,
fuchsia = 0xFF00FF,
crimson_purple = 0xE238EC,
heliotrope_purple = 0xD462FF,
tyrian_purple = 0xC45AEC,
medium_orchid = 0xBA55D3,
purple_flower = 0xA74AC7,
orchid_purple = 0xB048B5,
rich_lilac = 0xB666D2,
pastel_violet = 0xD291BC,
mauve_taupe = 0x915F6D,
viola_purple = 0x7E587E,
eggplant = 0x614051,
plum_purple = 0x583759,
grape = 0x5E5A80,
purple_navy = 0x4E5180,
slate_blue = 0x6A5ACD,
blue_lotus = 0x6960EC,
blurple = 0x5865F2,
light_slate_blue = 0x736AFF,
medium_slate_blue = 0x7B68EE,
periwinkle_purple = 0x7575CF,
very_peri = 0x6667AB,
bright_grape = 0x6F2DA8,
purple_amethyst = 0x6C2DC7,
bright_purple = 0x6A0DAD,
deep_periwinkle = 0x5453A6,
dark_slate_blue = 0x483D8B,
purple_haze = 0x4E387E,
purple_iris = 0x571B7E,
dark_purple = 0x4B0150,
deep_purple = 0x36013F,
midnight_purple = 0x2E1A47,
purple_monster = 0x461B7E,
indigo = 0x4B0082,
blue_whale = 0x342D7E,
rebecca_purple = 0x663399,
purple_jam = 0x6A287E,
dark_magenta = 0x8B008B,
purple = 0x800080,
french_lilac = 0x86608E,
dark_orchid = 0x9932CC,
dark_violet = 0x9400D3,
purple_violet = 0x8D38C9,
jasmine_purple = 0xA23BEC,
purple_daffodil = 0xB041FF,
clematis_violet = 0x842DCE,
blue_violet = 0x8A2BE2,
purple_sage_bush = 0x7A5DC7,
lovely_purple = 0x7F38EC,
neon_purple = 0x9D00FF,
purple_plum = 0x8E35EF,
aztech_purple = 0x893BFF,
medium_purple = 0x9370DB,
light_purple = 0x8467D7,
crocus_purple = 0x9172EC,
purple_mimosa = 0x9E7BFF,
periwinkle = 0xCCCCFF,
pale_lilac = 0xDCD0FF,
lavender_purple = 0x967BB6,
rose_purple = 0xB09FCA,
lilac = 0xC8A2C8,
mauve = 0xE0B0FF,
bright_lilac = 0xD891EF,
purple_dragon = 0xC38EC7,
plum = 0xDDA0DD,
blush_pink = 0xE6A9EC,
pastel_purple = 0xF2A2E8,
blossom_pink = 0xF9B7FF,
wisteria_purple = 0xC6AEC7,
purple_thistle = 0xD2B9D3,
thistle = 0xD8BFD8,
purple_white = 0xDFD3E3,
periwinkle_pink = 0xE9CFEC,
cotton_candy = 0xFCDFFF,
lavender_pinocchio = 0xEBDDE2,
dark_white = 0xE1D9D1,
ash_white = 0xE9E4D4,
white_chocolate = 0xEDE6D6,
soft_ivory = 0xFAF0DD,
off_white = 0xF8F0E3,
pearl_white = 0xF8F6F0,
red_white = 0xF3E8EA,
lavender_blush = 0xFFF0F5,
pearl = 0xFDEEF4,
egg_shell = 0xFFF9E3,
old_lace = 0xFEF0E3,
linen = 0xFAF0E6,
sea_shell = 0xFFF5EE,
bone_white = 0xF9F6EE,
rice = 0xFAF5EF,
floral_white = 0xFFFAF0,
ivory = 0xFFFFF0,
white_gold = 0xFFFFF4,
light_white = 0xFFFFF7,
white_smoke = 0xF5F5F5,
cotton = 0xFBFBF9,
snow = 0xFFFAFA,
milk_white = 0xFEFCFF,
half_white = 0xFFFEFA;
} // namespace colors
/**
* @brief Predefined colour constants, same as colors but for the british.
*/
namespace colours = colors;
} // namespace dpp

View File

@ -0,0 +1,428 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/misc-enum.h>
#include <dpp/user.h>
#include <dpp/guild.h>
#include <dpp/role.h>
#include <dpp/appcommand.h>
#include <dpp/dispatcher.h>
#include <dpp/utility.h>
#include <dpp/json_fwd.h>
#include <dpp/event_router.h>
#include <unordered_map>
#include <vector>
#include <functional>
#include <variant>
namespace dpp {
/**
* @brief dpp::resolved_user contains both a dpp::guild_member and a dpp::user.
* The user can be used to obtain in-depth user details such as if they are nitro,
* and the guild member information to check their roles on a guild etc.
* The Discord API provides both if a parameter is a user ping,
* so we offer both in a combined structure.
*/
struct DPP_EXPORT resolved_user {
/**
* @brief Holds user information
*/
dpp::user user;
/**
* @brief Holds member information
*/
dpp::guild_member member;
};
/**
* @brief Represents a received parameter.
* We use variant so that multiple non-related types can be contained within.
*/
typedef std::variant<std::monostate, std::string, dpp::role, dpp::channel, dpp::resolved_user, int64_t, bool, double> command_parameter;
/**
* @brief Parameter types when registering a command.
* We don't pass these in when triggering the command in the handler, because it is
* expected the developer added the command so they know what types to expect for each named
* parameter.
*/
enum parameter_type {
/**
* @brief String parameter.
*/
pt_string,
/**
* @brief Role object parameter.
*/
pt_role,
/**
* @brief Channel object parameter.
*/
pt_channel,
/**
* @brief User object parameter.
*/
pt_user,
/**
* @brief 64 bit signed integer parameter.
*/
pt_integer,
/**
* @brief double floating point parameter.
*/
pt_double,
/**
* @brief Boolean parameter.
*/
pt_boolean
};
/**
* @brief Details of a command parameter used in registration.
* Note that for non-slash commands optional parameters can only be at the end of
* the list of parameters.
*/
struct DPP_EXPORT param_info {
/**
* @brief Type of parameter
*/
parameter_type type;
/**
* @brief True if the parameter is optional.
* For non-slash commands optional parameters may only be on the end of the list.
*/
bool optional;
/**
* @brief Description of command. Displayed only for slash commands
*/
std::string description;
/**
* @brief Allowed multiple choice options.
* The key name is the string passed to the command handler
* and the key value is its description displayed to the user.
*/
std::map<command_value, std::string> choices;
/**
* @brief Construct a new param_info object
*
* @param t Type of parameter
* @param o True if parameter is optional
* @param description The parameter description
* @param opts The options for a multiple choice parameter
*/
param_info(parameter_type t, bool o, const std::string &description, const std::map<command_value, std::string> &opts = {});
};
/**
* @brief Parameter list used during registration.
* Note that use of vector/pair is important here to preserve parameter order,
* as opposed to unordered_map (which doesn't guarantee any order at all) and
* std::map, which reorders keys alphabetically.
*/
typedef std::vector<std::pair<std::string, param_info>> parameter_registration_t;
/**
* @brief Parameter list for a called command.
* See dpp::parameter_registration_t for an explanation as to why vector is used.
*/
typedef std::vector<std::pair<std::string, command_parameter>> parameter_list_t;
/**
* @brief Represents the sending source of a command.
* This is passed to any command handler and should be passed back to
* commandhandler::reply(), allowing the reply method to route any replies back
* to the origin, which may be a slash command or a message. Both require different
* response facilities but we want this to be transparent if you use the command
* handler class.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
struct DPP_EXPORT command_source {
/**
* @brief Sending guild id
*/
snowflake guild_id;
/**
* @brief Source channel id
*/
snowflake channel_id;
/**
* @brief Command ID of a slash command
*/
snowflake command_id;
/**
* @brief Token for sending a slash command reply
*/
std::string command_token;
/**
* @brief The user who issued the command
*/
user issuer;
/**
* @brief Copy of the underlying message_create_t event, if it was a message create event
*/
std::optional<message_create_t> message_event;
/**
* @brief Copy of the underlying interaction_create_t event, if it was an interaction create event
*/
std::optional<interaction_create_t> interaction_event;
/**
* @brief Construct a command_source object from a message_create_t event
*/
command_source(const struct message_create_t& event);
/**
* @brief Construct a command_source object from an interaction_create_t event
*/
command_source(const struct interaction_create_t& event);
};
/**
* @brief The function definition for a command handler. Expects a command name string,
* and a list of command parameters.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
typedef std::function<void(const std::string&, const parameter_list_t&, command_source)> command_handler;
/**
* @brief Represents the details of a command added to the command handler class.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
struct DPP_EXPORT command_info_t {
/**
* @brief Function reference for the handler. This is std::function so it can represent
* a class member, a lambda or a raw C function pointer.
*/
command_handler func;
/**
* @brief Parameters requested for the command, with their types
*/
parameter_registration_t parameters;
/**
* @brief Guild ID the command exists on, or 0 to be present on all guilds
*/
snowflake guild_id;
};
/**
* @brief The commandhandler class represents a group of commands, prefixed or slash commands with handling functions.
*
* It can automatically register slash commands, and handle routing of messages and interactions to separated command handler
* functions.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
class DPP_EXPORT DPP_DEPRECATED("commandhandler should not be used. Please consider using dpp::cluster::register_command instead.") commandhandler {
private:
/**
* @brief List of guild commands to bulk register
*/
std::map<dpp::snowflake, std::vector<dpp::slashcommand>> bulk_registration_list_guild;
/**
* @brief List of global commands to bulk register
*/
std::vector<dpp::slashcommand> bulk_registration_list_global;
public:
/**
* @brief Commands in the handler
*/
std::unordered_map<std::string, command_info_t> commands;
/**
* @brief Valid prefixes
*/
std::vector<std::string> prefixes;
/**
* @brief Set to true automatically if one of the prefixes added is "/"
*/
bool slash_commands_enabled;
/**
* @brief Cluster we are attached to for issuing REST calls
*/
class cluster* owner;
/**
* @brief Application ID
*/
snowflake app_id;
/**
* @brief Interaction event handle
*/
event_handle interactions;
/**
* @brief Message event handle
*/
event_handle messages;
/**
* @brief Returns true if the string has a known prefix on the start.
* Modifies string to remove prefix if it returns true.
*
* @param str String to check and modify
* @return true string contained a prefix, prefix removed from string
* @return false string did not contain a prefix
*/
bool string_has_prefix(std::string &str);
public:
/**
* @brief Construct a new commandhandler object
*
* @param o Owning cluster to attach to
* @param auto_hook_events Set to true to automatically hook the on_slashcommand
* and on_message events. You should not need to set this to false unless you have a specific
* use case, as D++ supports multiple listeners to an event, so will allow the commandhandler
* to hook to your command events without disrupting other uses for the events you may have.
* @param application_id The application id of the bot. If not specified, the class will
* look within the cluster object and use cluster::me::id instead.
*/
commandhandler(class cluster* o, bool auto_hook_events = true, snowflake application_id = 0);
/**
* @brief Destroy the commandhandler object
*/
~commandhandler();
/**
* @brief Set the application id after construction
*
* @param o Owning cluster to attach to
*/
commandhandler& set_owner(class cluster* o);
/**
* @brief Add a prefix to the command handler
*
* @param prefix Prefix to be handled by the command handler
* @return commandhandler& reference to self
*/
commandhandler& add_prefix(const std::string &prefix);
/**
* @brief Add a command to the command handler
*
* @param command Command to be handled.
* Note that if any one of your prefixes is "/" this will attempt to register
* a global command using the API and you will receive notification of this command
* via an interaction event.
* @param handler Handler function
* @param parameters Parameters to use for the command
* @param description The description of the command, shown for slash commands
* @param guild_id The guild ID to restrict the command to. For slash commands causes registration of a guild command as opposed to a global command.
* @return commandhandler& reference to self
* @throw dpp::logic_exception if application ID cannot be determined
*/
commandhandler& add_command(const std::string &command, const parameter_registration_t &parameters, command_handler handler, const std::string &description = "", snowflake guild_id = 0);
/**
* @brief Register all slash commands with Discord
* This method must be called at least once if you are using the "/" prefix to mark the
* end of commands being added to the handler. Note that this uses bulk registration and will replace any
* existing slash commands.
*
* Note that if you have previously registered your commands and they have not changed, you do
* not need to call this again. Discord retains a cache of previously added commands.
*
* @return commandhandler& Reference to self for chaining method calls
*/
commandhandler& register_commands();
/**
* @brief Route a command from the on_message_create function.
* Call this method from within your on_message_create with the received
* dpp::message object if you have disabled automatic registration of events.
*
* @param event message create event to parse
*/
void route(const struct dpp::message_create_t& event);
/**
* @brief Route a command from the on_slashcommand function.
* Call this method from your on_slashcommand with the received
* dpp::interaction_create_t object if you have disabled automatic registration of events.
*
* @param event command interaction event to parse
*/
void route(const struct slashcommand_t & event);
/**
* @brief Reply to a command.
* You should use this method rather than cluster::message_create as
* the way you reply varies between slash commands and message commands.
* Note you should ALWAYS reply. Slash commands will emit an ugly error
* to the user if you do not emit some form of reply within 3 seconds.
*
* @param m message to reply with.
* @param source source of the command
* @param callback User function to execute when the api call completes.
*/
void reply(const dpp::message &m, command_source source, command_completion_event_t callback = utility::log_error());
/**
* @brief Reply to a command without a message, causing the discord client
* to display "Bot name is thinking...".
* The "thinking" message will persist for a maximum of 15 minutes.
* This counts as a reply for a slash command. Slash commands will emit an
* ugly error to the user if you do not emit some form of reply within 3
* seconds.
*
* @param source source of the command
* @param callback User function to execute when the api call completes.
*/
void thinking(command_source source, command_completion_event_t callback = utility::log_error());
/**
* @brief Easter egg (redefinition of dpp::commandhandler::thinking).
*/
void thonk(command_source source, command_completion_event_t callback = utility::log_error());
};
}

29
include/dpp/coro.h Normal file
View File

@ -0,0 +1,29 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include "coro/awaitable.h"
#include "coro/async.h"
#include "coro/coroutine.h"
#include "coro/job.h"
#include "coro/task.h"
#include "coro/when_any.h"

189
include/dpp/coro/async.h Normal file
View File

@ -0,0 +1,189 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
#include <dpp/coro/awaitable.h>
namespace dpp {
struct async_dummy : awaitable_dummy {
std::shared_ptr<int> dummy_shared_state = nullptr;
};
}
#ifndef DPP_NO_CORO
#include "coro.h"
#include <utility>
#include <type_traits>
#include <functional>
#include <atomic>
#include <cstddef>
namespace dpp {
namespace detail {
namespace async {
/**
* @brief Shared state of the async and its callback, to be used across threads.
*/
template <typename R>
struct callback {
/**
* @brief Promise object to set the result into
*/
std::shared_ptr<basic_promise<R>> promise{nullptr};
/**
* @brief Call operator, sets the value in the promise and notifies any awaiter
*
* @param v Callback value
*/
template <typename U = R>
void operator()(const U& v) const requires (std::convertible_to<const U&, R>) {
promise->set_value(v);
}
/**
* @brief Call operator, sets the value in the promise and notifies any awaiter
*
* @param v Callback value
*/
template <typename U = R>
void operator()(U&& v) const requires (std::convertible_to<U&&, R>) {
promise->set_value(std::move(v));
}
/**
* @brief Call operator, sets the value in the promise and notifies any awaiter
*/
void operator()() const requires (std::is_void_v<R>)
{
promise->set_value();
}
};
} // namespace async
} // namespace detail
struct confirmation_callback_t;
/**
* @class async async.h coro/async.h
* @brief A co_await-able object handling an API call in parallel with the caller.
*
* This class is the return type of the dpp::cluster::co_* methods, but it can also be created manually to wrap any async call.
*
* @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables.
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
* @tparam R The return type of the API call. Defaults to confirmation_callback_t
*/
template <typename R>
class async : public awaitable<R> {
/**
* @brief Callable object to pass to API calls
*/
detail::async::callback<R> api_callback{};
/**
* @brief Internal promise constructor, grabs a promise object for the callback to use
*/
explicit async(std::shared_ptr<basic_promise<R>> &&promise) : awaitable<R>{promise.get()}, api_callback{std::move(promise)} {}
public:
using awaitable<R>::awaitable; // use awaitable's constructors
using awaitable<R>::operator=; // use async_base's assignment operator
using awaitable<R>::await_ready; // expose await_ready as public
/**
* @brief The return type of the API call. Defaults to confirmation_callback_t
*/
using result_type = R;
/**
* @brief Construct an async object wrapping an object method, the call is made immediately by forwarding to <a href="https://en.cppreference.com/w/cpp/utility/functional/invoke">std::invoke</a> and can be awaited later to retrieve the result.
*
* @param obj The object to call the method on
* @param fun The method of the object to call. Its last parameter must be a callback taking a parameter of type R
* @param args Parameters to pass to the method, excluding the callback
*/
template <typename Obj, typename Fun, typename... Args>
#ifndef _DOXYGEN_
requires std::invocable<Fun, Obj, Args..., std::function<void(R)>>
#endif
explicit async(Obj &&obj, Fun &&fun, Args&&... args) : async{std::make_shared<basic_promise<R>>()} {
std::invoke(std::forward<Fun>(fun), std::forward<Obj>(obj), std::forward<Args>(args)..., api_callback);
}
/**
* @brief Construct an async object wrapping an invokeable object, the call is made immediately by forwarding to <a href="https://en.cppreference.com/w/cpp/utility/functional/invoke">std::invoke</a> and can be awaited later to retrieve the result.
*
* @param fun The object to call using <a href="https://en.cppreference.com/w/cpp/utility/functional/invoke">std::invoke</a>. Its last parameter must be a callable taking a parameter of type R
* @param args Parameters to pass to the object, excluding the callback
*/
template <typename Fun, typename... Args>
#ifndef _DOXYGEN_
requires std::invocable<Fun, Args..., std::function<void(R)>>
#endif
explicit async(Fun &&fun, Args&&... args) : async{std::make_shared<basic_promise<R>>()} {
std::invoke(std::forward<Fun>(fun), std::forward<Args>(args)..., api_callback);
}
/**
* @brief Copy constructor is disabled.
*/
async(const async&) = delete;
/**
* @brief Move constructor, moves the awaitable async object
*/
async(async&&) = default;
/**
* @brief Copy assignment operator is disabled.
*/
async& operator=(const async&) = delete;
/**
* @brief Move assignment operator, moves the awaitable async object
*/
async& operator=(async&&) = default;
/**
* @brief Destructor, signals to the callback that the async object is gone and shouldn't be notified of the result
*/
~async() {
this->abandon();
}
};
DPP_CHECK_ABI_COMPAT(async<>, async_dummy);
}
#endif /* DPP_NO_CORO */

View File

@ -0,0 +1,735 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <iostream>
#include <dpp/utility.h>
namespace dpp {
struct awaitable_dummy {
int *promise_dummy = nullptr;
};
}
#ifndef DPP_NO_CORO
#include <dpp/coro/coro.h>
// Do not include <coroutine> as coro.h includes <experimental/coroutine> or <coroutine> depending on clang version
#include <mutex>
#include <optional>
#include <utility>
#include <type_traits>
#include <exception>
#include <atomic>
#include <condition_variable>
#include <cstdint>
namespace dpp {
namespace detail::promise {
/**
* @brief State of a promise
*/
enum state_flags {
/**
* @brief Promise is empty
*/
sf_none = 0b0000000,
/**
* @brief Promise has spawned an awaitable
*/
sf_has_awaitable = 0b00000001,
/**
* @brief Promise is being awaited
*/
sf_awaited = 0b00000010,
/**
* @brief Promise has a result
*/
sf_ready = 0b00000100,
/**
* @brief Promise has completed, no more results are expected
*/
sf_done = 0b00001000,
/**
* @brief Promise was broken - future or promise is gone
*/
sf_broken = 0b0010000
};
template <typename T>
class promise_base;
/**
* @brief Empty result from void-returning awaitable
*/
struct empty{};
/**
* @brief Variant for the 3 conceptual values of a coroutine:
*/
template <typename T>
using result_t = std::variant<std::monostate, std::conditional_t<std::is_void_v<T>, empty, T>, std::exception_ptr>;
template <typename T>
void spawn_sync_wait_job(auto* awaitable, std::condition_variable &cv, auto&& result);
} /* namespace detail::promise */
template <typename Derived>
class basic_awaitable {
protected:
/**
* @brief Implementation for sync_wait. This is code used by sync_wait, sync_wait_for, sync_wait_until.
*
* @tparam Timed Whether the wait function times out or not
* @param do_wait Function to do the actual wait on the cv
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
*/
template <bool Timed>
auto sync_wait_impl(auto&& do_wait) {
using result_type = decltype(detail::co_await_resolve(std::declval<Derived>()).await_resume());
using variant_type = detail::promise::result_t<result_type>;
variant_type result;
std::condition_variable cv;
detail::promise::spawn_sync_wait_job<result_type>(static_cast<Derived*>(this), cv, result);
do_wait(cv, result);
/*
* Note: we use .index() here to support dpp::promise<std::exception_ptr> & dpp::promise<std::monostate> :D
*/
if (result.index() == 2) {
std::rethrow_exception(std::get<2>(result));
}
if constexpr (!Timed) { // no timeout
if constexpr (!std::is_void_v<result_type>) {
return std::get<1>(result);
}
} else { // timeout
if constexpr (std::is_void_v<result_type>) {
return result.index() == 1 ? true : false;
} else {
return result.index() == 1 ? std::optional<result_type>{std::get<1>(result)} : std::nullopt;
}
}
}
public:
/**
* @brief Blocks this thread and waits for the awaitable to finish.
*
* @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead.
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
*/
auto sync_wait() {
return sync_wait_impl<false>([](std::condition_variable &cv, auto&& result) {
std::mutex m{};
std::unique_lock lock{m};
cv.wait(lock, [&result] { return result.index() != 0; });
});
}
/**
* @brief Blocks this thread and waits for the awaitable to finish.
*
* @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead.
* @param duration Maximum duration to wait for
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
*/
template <class Rep, class Period>
auto sync_wait_for(const std::chrono::duration<Rep, Period>& duration) {
return sync_wait_impl<true>([duration](std::condition_variable &cv, auto&& result) {
std::mutex m{};
std::unique_lock lock{m};
cv.wait_for(lock, duration, [&result] { return result.index() != 0; });
});
}
/**
* @brief Blocks this thread and waits for the awaitable to finish.
*
* @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead.
* @param time Maximum time point to wait for
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
*/
template <class Clock, class Duration>
auto sync_wait_until(const std::chrono::time_point<Clock, Duration> &time) {
return sync_wait_impl<true>([time](std::condition_variable &cv, auto&& result) {
std::mutex m{};
std::unique_lock lock{m};
cv.wait_until(lock, time, [&result] { return result.index() != 0; });
});
}
};
/**
* @brief Generic awaitable class, represents a future value that can be co_await-ed on.
*
* Roughly equivalent of std::future for coroutines, with the crucial distinction that the future does not own a reference to a "shared state".
* It holds a non-owning reference to the promise, which must be kept alive for the entire lifetime of the awaitable.
*
* @tparam T Type of the asynchronous value
* @see promise
*/
template <typename T>
class awaitable : public basic_awaitable<awaitable<T>> {
protected:
friend class detail::promise::promise_base<T>;
using shared_state = detail::promise::promise_base<T>;
using state_flags = detail::promise::state_flags;
/**
* @brief The type of the result produced by this task.
*/
using result_type = T;
/**
* @brief Non-owning pointer to the promise, which must be kept alive for the entire lifetime of the awaitable.
*/
shared_state *state_ptr = nullptr;
/**
* @brief Construct from a promise.
*
* @param promise The promise to refer to.
*/
awaitable(shared_state *promise) noexcept : state_ptr{promise} {}
/**
* @brief Abandons the promise.
*
* Set the promise's state to broken and unlinks this awaitable.
*
* @return uint8_t Flags previously held before setting them to broken
*/
uint8_t abandon();
/**
* @brief Awaiter returned by co_await.
*
* Contains the await_ready, await_suspend and await_resume functions required by the C++ standard.
* This class is CRTP-like, in that it will refer to an object derived from awaitable.
*
* @tparam Derived Type of reference to refer to the awaitable.
*/
template <typename Derived>
struct awaiter {
Derived awaitable_obj;
/**
* @brief First function called by the standard library when co_await-ing this object.
*
* @throws dpp::logic_exception If the awaitable's valid() would return false.
* @return bool Whether the result is ready, in which case we don't need to suspend
*/
bool await_ready() const;
/**
* @brief Second function called by the standard library when co_await-ing this object.
*
* @throws dpp::logic_exception If the awaitable's valid() would return false.
* At this point the coroutine frame was allocated and suspended.
*
* @return bool Whether we do need to suspend or not
*/
bool await_suspend(detail::std_coroutine::coroutine_handle<> handle);
/**
* @brief Third and final function called by the standard library when co_await-ing this object, after resuming.
*
* @throw ? Any exception that occured during the retrieval of the value will be thrown
* @return T The result.
*/
T await_resume();
};
public:
/**
* @brief Construct an empty awaitable.
*
* Such an awaitable must be assigned a promise before it can be awaited.
*/
awaitable() = default;
/**
* @brief Copy construction is disabled.
*/
awaitable(const awaitable&) = delete;
/**
* @brief Move from another awaitable.
*
* @param rhs The awaitable to move from, left in an unspecified state after this.
*/
awaitable(awaitable&& rhs) noexcept : state_ptr(std::exchange(rhs.state_ptr, nullptr)) {
}
/**
* @brief Title :)
*
* We use this in the destructor
*/
void if_this_causes_an_invalid_read_your_promise_was_destroyed_before_your_awaitable____check_your_promise_lifetime() {
abandon();
}
/**
* @brief Destructor.
*
* May signal to the promise that it was destroyed.
*/
~awaitable();
/**
* @brief Copy assignment is disabled.
*/
awaitable& operator=(const awaitable&) = delete;
/**
* @brief Move from another awaitable.
*
* @param rhs The awaitable to move from, left in an unspecified state after this.
* @return *this
*/
awaitable& operator=(awaitable&& rhs) noexcept {
abandon();
state_ptr = std::exchange(rhs.state_ptr, nullptr);
return *this;
}
/**
* @brief Check whether this awaitable refers to a valid promise.
*
* @return bool Whether this awaitable refers to a valid promise or not
*/
bool valid() const noexcept;
/**
* @brief Check whether or not co_await-ing this would suspend the caller, i.e. if we have the result or not
*
* @return bool Whether we already have the result or not
*/
bool await_ready() const;
/**
* @brief Overload of the co_await operator.
*
* @return Returns an @ref awaiter referencing this awaitable.
*/
template <typename Derived>
requires (std::is_base_of_v<awaitable, std::remove_cv_t<Derived>>)
friend awaiter<Derived&> operator co_await(Derived& obj) noexcept {
return {obj};
}
/**
* @brief Overload of the co_await operator. Returns an @ref awaiter referencing this awaitable.
*
* @return Returns an @ref awaiter referencing this awaitable.
*/
template <typename Derived>
requires (std::is_base_of_v<awaitable, std::remove_cv_t<Derived>>)
friend awaiter<Derived&&> operator co_await(Derived&& obj) noexcept {
return {std::move(obj)};
}
};
namespace detail::promise {
/**
* @brief Base class defining logic common to all promise types, aka the "write" end of an awaitable.
*/
template <typename T>
class promise_base {
protected:
friend class awaitable<T>;
/**
* @brief Variant representing one of either 3 states of the result value : empty, result, exception.
*/
using storage_type = result_t<T>;
/**
* @brief State of the result value.
*
* @see storage_type
*
* @note use .index() instead of std::holds_alternative to support promise_base<std::exception_ptr> and promise_base<std::monostate> :)
*/
storage_type value = std::monostate{};
/**
* @brief State of the awaitable tied to this promise.
*/
std::atomic<uint8_t> state = sf_none;
/**
* @brief Coroutine handle currently awaiting the completion of this promise.
*/
std_coroutine::coroutine_handle<> awaiter = nullptr;
/**
* @brief Check if the result is empty, throws otherwise.
*
* @throw dpp::logic_exception if the result isn't empty.
*/
void throw_if_not_empty() {
if (value.index() != 0) [[unlikely]] {
throw dpp::logic_exception("cannot set a value on a promise that already has one");
}
}
/**
* @brief Unlinks this promise from its currently linked awaiter and returns it.
*
* At the time of writing this is only used in the case of a serious internal error in dpp::task.
* Avoid using this as this will crash if the promise is used after this.
*/
std_coroutine::coroutine_handle<> release_awaiter() {
return std::exchange(awaiter, nullptr);
}
/**
* @brief Construct a new promise, with empty result.
*/
promise_base() = default;
/**
* @brief Copy construction is disabled.
*/
promise_base(const promise_base&) = delete;
/**
* @brief Move construction is disabled.
*
* awaitable hold a pointer to this object so moving is not possible.
*/
promise_base(promise_base&& rhs) = delete;
public:
/**
* @brief Copy assignment is disabled.
*/
promise_base &operator=(const promise_base&) = delete;
/**
* @brief Move assignment is disabled.
*/
promise_base &operator=(promise_base&& rhs) = delete;
/**
* @brief Set this promise to an exception and resume any awaiter.
*
* @tparam Notify Whether to resume any awaiter or not.
* @throws dpp::logic_exception if the promise is not empty.
* @throws ? Any exception thrown by the coroutine if resumed will propagate
*/
template <bool Notify = true>
void set_exception(std::exception_ptr ptr) {
throw_if_not_empty();
value.template emplace<2>(std::move(ptr));
[[maybe_unused]] auto previous_value = this->state.fetch_or(sf_ready, std::memory_order_acq_rel);
if constexpr (Notify) {
if ((previous_value & sf_awaited) != 0) {
this->awaiter.resume();
}
}
}
/**
* @brief Notify a currently awaiting coroutine that the result is ready.
*
* @note This may resume the coroutine on the current thread.
* @throws ? Any exception thrown by the coroutine if resumed will propagate
*/
void notify_awaiter() {
if ((state.load(std::memory_order_acquire) & sf_awaited) != 0) {
awaiter.resume();
}
}
/**
* @brief Get an awaitable object for this promise.
*
* @throws dpp::logic_exception if get_awaitable has already been called on this object.
* @return awaitable<T> An object that can be co_await-ed to retrieve the value of this promise.
*/
awaitable<T> get_awaitable() {
uint8_t previous_flags = state.fetch_or(sf_has_awaitable, std::memory_order_relaxed);
if (previous_flags & sf_has_awaitable) [[unlikely]] {
throw dpp::logic_exception{"an awaitable was already created from this promise"};
}
return { this };
}
};
}
/**
* @brief Generic promise class, represents the owning potion of an asynchronous value.
*
* This class is roughly equivalent to std::promise, with the crucial distinction that the promise *IS* the shared state.
* As such, the promise needs to be kept alive for the entire time a value can be retrieved.
*
* @tparam T Type of the asynchronous value
* @see awaitable
*/
template <typename T>
class basic_promise : public detail::promise::promise_base<T> {
public:
using detail::promise::promise_base<T>::promise_base;
using detail::promise::promise_base<T>::operator=;
/**
* @brief Construct the result in place by forwarding the arguments, and by default resume any awaiter.
*
* @tparam Notify Whether to resume any awaiter or not.
* @throws dpp::logic_exception if the promise is not empty.
*/
template <bool Notify = true, typename... Args>
requires (std::constructible_from<T, Args...>)
void emplace_value(Args&&... args) {
this->throw_if_not_empty();
try {
this->value.template emplace<1>(std::forward<Args>(args)...);
} catch (...) {
this->value.template emplace<2>(std::current_exception());
}
[[maybe_unused]] auto previous_value = this->state.fetch_or(detail::promise::sf_ready, std::memory_order_acq_rel);
if constexpr (Notify) {
if (previous_value & detail::promise::sf_awaited) {
this->awaiter.resume();
}
}
}
/**
* @brief Construct the result by forwarding reference, and resume any awaiter.
*
* @tparam Notify Whether to resume any awaiter or not.
* @throws dpp::logic_exception if the promise is not empty.
*/
template <bool Notify = true, typename U = T>
requires (std::convertible_to<U&&, T>)
void set_value(U&& v) {
emplace_value<Notify>(std::forward<U>(v));
}
/**
* @brief Construct a void result, and resume any awaiter.
*
* @tparam Notify Whether to resume any awaiter or not.
* @throws dpp::logic_exception if the promise is not empty.
*/
template <bool Notify = true>
requires (std::is_void_v<T>)
void set_value() {
this->throw_if_not_empty();
this->value.template emplace<1>();
[[maybe_unused]] auto previous_value = this->state.fetch_or(detail::promise::sf_ready, std::memory_order_acq_rel);
if constexpr (Notify) {
if (previous_value & detail::promise::sf_awaited) {
this->awaiter.resume();
}
}
}
};
/**
* @brief Generic promise class, represents the owning potion of an asynchronous value.
*
* This class is roughly equivalent to std::promise, with the crucial distinction that the promise *IS* the shared state.
* As such, the promise needs to be kept alive for the entire time a value can be retrieved.
*
* The difference between basic_promise and this object is that this one is moveable as it wraps an underlying basic_promise in a std::unique_ptr.
*
* @see awaitable
*/
template <typename T>
class moveable_promise {
/**
* @brief Shared state, wrapped in a unique_ptr to allow move without disturbing an awaitable's promise pointer.
*/
std::unique_ptr<basic_promise<T>> shared_state = std::make_unique<basic_promise<T>>();
public:
/**
* @copydoc basic_promise<T>::emplace_value
*/
template <bool Notify = true, typename... Args>
requires (std::constructible_from<T, Args...>)
void emplace_value(Args&&... args) {
shared_state->template emplace_value<Notify>(std::forward<Args>(args)...);
}
/**
* @copydoc basic_promise<T>::set_value(U&&)
*/
template <bool Notify = true, typename U = T>
void set_value(U&& v) requires (std::convertible_to<U&&, T>) {
shared_state->template set_value<Notify>(std::forward<U>(v));
}
/**
* @copydoc basic_promise<T>::set_value()
*/
template <bool Notify = true>
void set_value() requires (std::is_void_v<T>) {
shared_state->template set_value<Notify>();
}
/**
* @copydoc basic_promise<T>::set_value(T&&)
*/
template <bool Notify = true>
void set_exception(std::exception_ptr ptr) {
shared_state->template set_exception<Notify>(std::move(ptr));
}
/**
* @copydoc basic_promise<T>::notify_awaiter
*/
void notify_awaiter() {
shared_state->notify_awaiter();
}
/**
* @copydoc basic_promise<T>::get_awaitable
*/
awaitable<T> get_awaitable() {
return shared_state->get_awaitable();
}
};
template <typename T>
using promise = moveable_promise<T>;
template <typename T>
auto awaitable<T>::abandon() -> uint8_t {
uint8_t previous_state = state_flags::sf_broken;
if (state_ptr) {
previous_state = state_ptr->state.fetch_or(state_flags::sf_broken, std::memory_order_acq_rel);
state_ptr = nullptr;
}
return previous_state;
}
template <typename T>
awaitable<T>::~awaitable() {
if_this_causes_an_invalid_read_your_promise_was_destroyed_before_your_awaitable____check_your_promise_lifetime();
}
template <typename T>
bool awaitable<T>::valid() const noexcept {
return state_ptr != nullptr;
}
template <typename T>
bool awaitable<T>::await_ready() const {
if (!this->valid()) {
throw dpp::logic_exception("cannot co_await an empty awaitable");
}
uint8_t state = this->state_ptr->state.load(std::memory_order_relaxed);
return state & detail::promise::sf_ready;
}
template <typename T>
template <typename Derived>
bool awaitable<T>::awaiter<Derived>::await_suspend(detail::std_coroutine::coroutine_handle<> handle) {
auto &promise = *awaitable_obj.state_ptr;
promise.awaiter = handle;
auto previous_flags = promise.state.fetch_or(detail::promise::sf_awaited, std::memory_order_relaxed);
if (previous_flags & detail::promise::sf_awaited) {
throw dpp::logic_exception("awaitable is already being awaited");
}
return !(previous_flags & detail::promise::sf_ready);
}
template <typename T>
template <typename Derived>
T awaitable<T>::awaiter<Derived>::await_resume() {
auto &promise = *awaitable_obj.state_ptr;
promise.state.fetch_and(static_cast<uint8_t>(~detail::promise::sf_awaited), std::memory_order_acq_rel);
if (std::holds_alternative<std::exception_ptr>(promise.value)) {
std::rethrow_exception(std::get<2>(promise.value));
}
if constexpr (!std::is_void_v<T>) {
return std::get<1>(std::move(promise.value));
} else {
return;
}
}
template <typename T>
template <typename Derived>
bool awaitable<T>::awaiter<Derived>::await_ready() const {
return static_cast<Derived>(awaitable_obj).await_ready();
}
}
#include <dpp/coro/job.h>
namespace dpp {
namespace detail::promise {
template <typename T>
void spawn_sync_wait_job(auto* awaitable, std::condition_variable &cv, auto&& result) {
[](auto* awaitable_, std::condition_variable &cv_, auto&& result_) -> dpp::job {
try {
if constexpr (std::is_void_v<T>) {
co_await *awaitable_;
result_.template emplace<1>();
} else {
result_.template emplace<1>(co_await *awaitable_);
}
} catch (...) {
result_.template emplace<2>(std::current_exception());
}
cv_.notify_all();
}(awaitable, cv, std::forward<decltype(result)>(result));
}
}
}
#endif /* DPP_NO_CORO */

205
include/dpp/coro/coro.h Normal file
View File

@ -0,0 +1,205 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#ifndef DPP_NO_CORO
#if (defined(_LIBCPP_VERSION) and !defined(__cpp_impl_coroutine)) // if libc++ experimental implementation (LLVM < 14)
# define STDCORO_EXPERIMENTAL_HEADER
# define STDCORO_EXPERIMENTAL_NAMESPACE
#endif
#ifdef STDCORO_GLIBCXX_COMPAT
# define __cpp_impl_coroutine 1
namespace std {
namespace experimental {
using namespace std;
}
}
#endif
#ifdef STDCORO_EXPERIMENTAL_HEADER
# include <experimental/coroutine>
#else
# include <coroutine>
#endif
namespace dpp {
/**
* @brief Implementation details for internal use only.
*
* @attention This is only meant to be used by D++ internally. Support will not be given regarding the facilities in this namespace.
*/
namespace detail {
#ifdef _DOXYGEN_
/**
* @brief Alias for either std or std::experimental depending on compiler and library. Used by coroutine implementation.
*
* @todo Remove and use std when all supported libraries have coroutines in it
*/
namespace std_coroutine {}
#else
# ifdef STDCORO_EXPERIMENTAL_NAMESPACE
namespace std_coroutine = std::experimental;
# else
namespace std_coroutine = std;
# endif
#endif
#ifndef _DOXYGEN_
/**
* @brief Concept to check if a type has a useable `operator co_await()` member
*/
template <typename T>
concept has_co_await_member = requires (T expr) { expr.operator co_await(); };
/**
* @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)`
*/
template <typename T>
concept has_free_co_await = requires (T expr) { operator co_await(expr); };
/**
* @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions.
*/
template <typename T>
concept has_await_members = requires (T expr) { expr.await_ready(); expr.await_suspend(); expr.await_resume(); };
/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*/
template <typename T>
requires (has_co_await_member<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(expr.operator co_await())) {
decltype(auto) awaiter = expr.operator co_await();
return awaiter;
}
/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*/
template <typename T>
requires (!has_co_await_member<T> && has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(operator co_await(expr))) {
decltype(auto) awaiter = operator co_await(static_cast<T&&>(expr));
return awaiter;
}
/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*/
template <typename T>
requires (!has_co_await_member<T> && !has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept {
return static_cast<T&&>(expr);
}
#else
/**
* @brief Concept to check if a type has a useable `operator co_await()` member
*
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
*/
template <typename T>
inline constexpr bool has_co_await_member;
/**
* @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)`
*
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
*/
template <typename T>
inline constexpr bool has_free_co_await;
/**
* @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions.
*
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
*/
template <typename T>
inline constexpr bool has_await_members;
/**
* @brief Concept to check if a type can be used with co_await
*
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
*/
template <typename T>
inline constexpr bool awaitable_type;
/**
* @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
*
* This function is conditionally noexcept, if the returned expression also is.
*/
decltype(auto) co_await_resolve(auto&& expr) {}
#endif
/**
* @brief Convenience alias for the result of a certain awaitable's await_resume.
*/
template <typename T>
using awaitable_result = decltype(co_await_resolve(std::declval<T>()).await_resume());
} // namespace detail
/**
* @brief Concept to check if a type can be used with co_await
*/
template <typename T>
concept awaitable_type = requires (T expr) { detail::co_await_resolve(expr).await_ready(); };
struct confirmation_callback_t;
template <typename R = confirmation_callback_t>
class async;
template <typename R = void>
#ifndef _DOXYGEN_
requires (!std::is_reference_v<R>)
#endif
class task;
template <typename R = void>
class coroutine;
struct job;
#ifdef DPP_CORO_TEST
/**
* @brief Allocation count of a certain type, for testing purposes.
*
* @todo Remove when coro is stable
*/
template <typename T>
inline int coro_alloc_count = 0;
#endif
} // namespace dpp
#endif /* DPP_NO_CORO */

View File

@ -0,0 +1,406 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
namespace dpp {
struct coroutine_dummy {
int *handle_dummy = nullptr;
};
}
#ifndef DPP_NO_CORO
#include <dpp/coro/coro.h>
#include <dpp/coro/awaitable.h>
#include <optional>
#include <type_traits>
#include <exception>
#include <utility>
#include <type_traits>
namespace dpp {
namespace detail {
namespace coroutine {
template <typename R>
struct promise_t;
template <typename R>
/**
* @brief Alias for the handle_t of a coroutine.
*/
using handle_t = std_coroutine::coroutine_handle<promise_t<R>>;
} // namespace coroutine
} // namespace detail
/**
* @class coroutine coroutine.h coro/coroutine.h
* @brief Base type for a coroutine, starts on co_await.
*
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs.
* Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub Issues</a> or to our <a href="https://discord.gg/dpp">Discord Server</a>.
* @warning - Using co_await on this object more than once is undefined behavior.
* @tparam R Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment.
*/
template <typename R>
class [[nodiscard("dpp::coroutine only starts when it is awaited, it will do nothing if discarded")]] coroutine : public basic_awaitable<coroutine<R>> {
/**
* @brief Promise has friend access for the constructor
*/
friend struct detail::coroutine::promise_t<R>;
/**
* @brief Coroutine handle.
*/
detail::coroutine::handle_t<R> handle{nullptr};
/**
* @brief Construct from a handle. Internal use only.
*/
coroutine(detail::coroutine::handle_t<R> h) : handle{h} {}
struct awaiter {
/**
* @brief Reference to the coroutine object being awaited.
*/
coroutine &coro;
/**
* @brief First function called by the standard library when the coroutine is co_await-ed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throws invalid_operation_exception if the coroutine is empty or finished.
* @return bool Whether the coroutine is done
*/
[[nodiscard]] bool await_ready() const {
if (!coro.handle) {
throw dpp::logic_exception("cannot co_await an empty coroutine");
}
return coro.handle.done();
}
/**
* @brief Second function called by the standard library when the coroutine is co_await-ed.
*
* Stores the calling coroutine in the promise to resume when this coroutine suspends.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @param caller The calling coroutine, now suspended
*/
template <typename T>
[[nodiscard]] detail::coroutine::handle_t<R> await_suspend(detail::std_coroutine::coroutine_handle<T> caller) noexcept {
coro.handle.promise().parent = caller;
return coro.handle;
}
/**
* @brief Final function called by the standard library when the coroutine is co_await-ed.
*
* Pops the coroutine's result and returns it.
* @remark Do not call this manually, use the co_await keyword instead.
*/
R await_resume() {
detail::coroutine::promise_t<R> &promise = coro.handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
if constexpr (!std::is_void_v<R>) {
return *std::exchange(promise.result, std::nullopt);
} else {
return; // unnecessary but makes lsp happy
}
}
};
public:
/**
* @brief The type of the result produced by this coroutine.
*/
using result_type = R;
/**
* @brief Default constructor, creates an empty coroutine.
*/
coroutine() = default;
/**
* @brief Copy constructor is disabled
*/
coroutine(const coroutine &) = delete;
/**
* @brief Move constructor, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine(coroutine &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {}
/**
* @brief Destructor, destroys the handle.
*/
~coroutine() {
if (handle) {
handle.destroy();
}
}
/**
* @brief Copy assignment is disabled
*/
coroutine &operator=(const coroutine &) = delete;
/**
* @brief Move assignment, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine &operator=(coroutine &&other) noexcept {
handle = std::exchange(other.handle, nullptr);
return *this;
}
[[nodiscard]] auto operator co_await() {
return awaiter{*this};
}
};
namespace detail::coroutine {
template <typename R>
struct final_awaiter;
#ifdef DPP_CORO_TEST
struct promise_t_base{};
#endif
/**
* @brief Promise type for coroutine.
*/
template <typename R>
struct promise_t {
/**
* @brief Handle of the coroutine co_await-ing this coroutine.
*/
std_coroutine::coroutine_handle<> parent{nullptr};
/**
* @brief Return value of the coroutine
*/
std::optional<R> result{};
/**
* @brief Pointer to an uncaught exception thrown by the coroutine
*/
std::exception_ptr exception{nullptr};
#ifdef DPP_CORO_TEST
promise_t() {
++coro_alloc_count<promise_t_base>;
}
~promise_t() {
--coro_alloc_count<promise_t_base>;
}
#endif
/**
* @brief Function called by the standard library when reaching the end of a coroutine
*
* @return final_awaiter<R> Resumes any coroutine co_await-ing on this
*/
[[nodiscard]] final_awaiter<R> final_suspend() const noexcept;
/**
* @brief Function called by the standard library when the coroutine start
*
* @return @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_always">std::suspend_always</a> Always suspend at the start, for a lazy start
*/
[[nodiscard]] std_coroutine::suspend_always initial_suspend() const noexcept {
return {};
}
/**
* @brief Function called when an exception escapes the coroutine
*
* Stores the exception to throw to the co_await-er
*/
void unhandled_exception() noexcept {
exception = std::current_exception();
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
void return_value(R&& expr) noexcept(std::is_nothrow_move_constructible_v<R>) requires std::move_constructible<R> {
result = static_cast<R&&>(expr);
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
void return_value(const R &expr) noexcept(std::is_nothrow_copy_constructible_v<R>) requires std::copy_constructible<R> {
result = expr;
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
template <typename T>
requires (!std::is_same_v<R, std::remove_cvref_t<T>> && std::convertible_to<T, R>)
void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v<T, R>) {
result = std::forward<T>(expr);
}
/**
* @brief Function called to get the coroutine object
*/
dpp::coroutine<R> get_return_object() {
return dpp::coroutine<R>{handle_t<R>::from_promise(*this)};
}
};
/**
* @brief Struct returned by a coroutine's final_suspend, resumes the continuation
*/
template <typename R>
struct final_awaiter {
/**
* @brief First function called by the standard library when reaching the end of a coroutine
*
* @return false Always return false, we need to suspend to resume the parent
*/
[[nodiscard]] bool await_ready() const noexcept {
return false;
}
/**
* @brief Second function called by the standard library when reaching the end of a coroutine.
*
* @return std::handle_t<> Coroutine handle to resume, this is either the parent if present or std::noop_coroutine()
*/
[[nodiscard]] std_coroutine::coroutine_handle<> await_suspend(std_coroutine::coroutine_handle<promise_t<R>> handle) const noexcept {
auto parent = handle.promise().parent;
return parent ? parent : std_coroutine::noop_coroutine();
}
/**
* @brief Function called by the standard library when this object is resumed
*/
void await_resume() const noexcept {}
};
template <typename R>
final_awaiter<R> promise_t<R>::final_suspend() const noexcept {
return {};
}
/**
* @brief Struct returned by a coroutine's final_suspend, resumes the continuation
*/
template <>
struct promise_t<void> {
/**
* @brief Handle of the coroutine co_await-ing this coroutine.
*/
std_coroutine::coroutine_handle<> parent{nullptr};
/**
* @brief Pointer to an uncaught exception thrown by the coroutine
*/
std::exception_ptr exception{nullptr};
/**
* @brief Function called by the standard library when reaching the end of a coroutine
*
* @return final_awaiter<R> Resumes any coroutine co_await-ing on this
*/
[[nodiscard]] final_awaiter<void> final_suspend() const noexcept {
return {};
}
/**
* @brief Function called by the standard library when the coroutine start
*
* @return @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_always">std::suspend_always</a> Always suspend at the start, for a lazy start
*/
[[nodiscard]] std_coroutine::suspend_always initial_suspend() const noexcept {
return {};
}
/**
* @brief Function called when an exception escapes the coroutine
*
* Stores the exception to throw to the co_await-er
*/
void unhandled_exception() noexcept {
exception = std::current_exception();
}
/**
* @brief Function called when co_return is used
*/
void return_void() const noexcept {}
/**
* @brief Function called to get the coroutine object
*/
[[nodiscard]] dpp::coroutine<void> get_return_object() {
return dpp::coroutine<void>{handle_t<void>::from_promise(*this)};
}
};
} // namespace detail
DPP_CHECK_ABI_COMPAT(coroutine<void>, coroutine_dummy)
DPP_CHECK_ABI_COMPAT(coroutine<uint64_t>, coroutine_dummy)
}
/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function.
*/
template<typename R, typename... Args>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::coroutine<R>, Args...> {
using promise_type = dpp::detail::coroutine::promise_t<R>;
};
#endif /* DPP_NO_CORO */

145
include/dpp/coro/job.h Normal file
View File

@ -0,0 +1,145 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
namespace dpp {
struct job_dummy {
};
}
#ifndef DPP_NO_CORO
#include "coro.h"
#include <type_traits>
#include <utility>
namespace dpp {
/**
* @class job job.h coro/job.h
* @brief Extremely light coroutine object designed to send off a coroutine to execute on its own.
* Can be used in conjunction with coroutine events via @ref dpp::event_router_t::operator()(F&&) "event routers", or on its own.
*
* This object stores no state and is the recommended way to use coroutines if you do not need to co_await the result.
*
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs.
* Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub Issues</a> or to our <a href="https://discord.gg/dpp">Discord Server</a>.
* @warning - It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing.
* At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes @ref lambdas-and-locals "dangling references".
* For this reason, `co_await` will error if any parameters are passed by reference.
* If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid.
* If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them.
*/
struct job {};
namespace detail {
namespace job {
#ifdef DPP_CORO_TEST
struct promise{};
#endif
/**
* @brief Coroutine promise type for a job
*/
template <typename... Args>
struct promise {
#ifdef DPP_CORO_TEST
promise() {
++coro_alloc_count<job_promise_base>;
}
~promise() {
--coro_alloc_count<job_promise_base>;
}
#endif
/**
* @brief Function called when the job is done.
*
* @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_never">std::suspend_never</a> Do not suspend at the end, destroying the handle immediately
*/
std_coroutine::suspend_never final_suspend() const noexcept {
return {};
}
/**
* @brief Function called when the job is started.
*
* @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_never">std::suspend_never</a> Do not suspend at the start, starting the job immediately
*/
std_coroutine::suspend_never initial_suspend() const noexcept {
return {};
}
/**
* @brief Function called to get the job object
*
* @return job
*/
dpp::job get_return_object() const noexcept {
return {};
}
/**
* @brief Function called when an exception is thrown and not caught.
*
* @throw Immediately rethrows the exception to the caller / resumer
*/
void unhandled_exception() const {
throw;
}
/**
* @brief Function called when the job returns. Does nothing.
*/
void return_void() const noexcept {}
};
} // namespace job
} // namespace detail
DPP_CHECK_ABI_COMPAT(job, job_dummy)
} // namespace dpp
/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function.
*/
template<typename... Args>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::job, Args...> {
/**
* @brief Promise type for this coroutine signature.
*
* When the coroutine is created from a lambda, that lambda is passed as a first parameter.
* Not ideal but we'll allow any callable that takes the rest of the arguments passed
*/
using promise_type = dpp::detail::job::promise<Args...>;
};
#endif /* DPP_NO_CORO */

446
include/dpp/coro/task.h Normal file
View File

@ -0,0 +1,446 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
#include <dpp/coro/awaitable.h>
namespace dpp {
struct task_dummy : awaitable_dummy {
int* handle_dummy = nullptr;
};
}
#ifndef DPP_NO_CORO
#include <dpp/coro/coro.h>
#include <utility>
#include <type_traits>
#include <optional>
#include <functional>
#include <mutex>
#include <exception>
#include <atomic>
#include <iostream> // std::cerr in final_suspend
namespace dpp {
namespace detail {
/* Internal cogwheels for dpp::task */
namespace task {
/**
* @brief A @ref dpp::task "task"'s promise_t type, with special logic for handling nested tasks.
*
* @tparam R Return type of the task
*/
template <typename R>
struct promise_t;
/**
* @brief The object automatically co_await-ed at the end of a @ref dpp::task "task". Ensures nested coroutine chains are resolved, and the promise_t cleans up if it needs to.
*
* @tparam R Return type of the task
*/
template <typename R>
struct final_awaiter;
/**
* @brief Alias for <a href="https://en.cppreference.com/w/cpp/coroutine/coroutine_handle"std::coroutine_handle</a> for a @ref dpp::task "task"'s @ref promise_t.
*
* @tparam R Return type of the task
*/
template <typename R>
using handle_t = std_coroutine::coroutine_handle<promise_t<R>>;
} // namespace task
} // namespace detail
/**
* @class task task.h coro/task.h
* @brief A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for parallel coroutines returning a value.
*
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs.
* Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub Issues</a> or to our <a href="https://discord.gg/dpp">Discord Server</a>.
* @tparam R Return type of the task. Cannot be a reference but can be void.
*/
template <typename R>
#ifndef _DOXYGEN_
requires (!std::is_reference_v<R>)
#endif
class [[nodiscard("dpp::task cancels itself on destruction. use co_await on it, or its sync_wait method")]] task : public awaitable<R> {
friend struct detail::task::promise_t<R>;
using handle_t = detail::task::handle_t<R>;
using state_flags = detail::promise::state_flags;
handle_t handle{};
protected:
/**
* @brief Construct from a coroutine handle. Internal use only
*/
explicit task(handle_t handle_) : awaitable<R>(&handle_.promise()), handle(handle_) {}
/**
* @brief Clean up our handle, cancelling any running task
*/
void cleanup() {
if (handle && this->valid()) {
if (this->abandon() & state_flags::sf_done) {
handle.destroy();
} else {
cancel();
}
handle = nullptr;
}
}
public:
/**
* @brief Default constructor, creates a task not bound to a coroutine.
*/
task() = default;
/**
* @brief Copy constructor is disabled
*/
task(const task &) = delete;
/**
* @brief Move constructor, grabs another task's coroutine handle
*
* @param other Task to move the handle from
*/
task(task &&other) noexcept : awaitable<R>(std::move(other)), handle(std::exchange(other.handle, nullptr)) {}
/**
* @brief Copy assignment is disabled
*/
task &operator=(const task &) = delete;
/**
* @brief Move assignment, grabs another task's coroutine handle
*
* @param other Task to move the handle from
*/
task &operator=(task &&other) noexcept {
cleanup();
handle = std::exchange(other.handle, nullptr);
awaitable<R>::operator=(std::move(other));
return *this;
}
/**
* @brief Destructor.
*
* Destroys the handle. If the task is still running, it will be cancelled.
*/
~task() {
cleanup();
}
/**
* @brief Function to check if the task has finished its execution entirely
*
* @return bool Whether the task is finished.
*/
[[nodiscard]] bool done() const noexcept {
return handle && (!this->valid() || handle.promise().state.load(std::memory_order_acq_rel) == state_flags::sf_done);
}
/**
* @brief Cancel the task, it will stop the next time it uses co_await. On co_await-ing this task, throws dpp::task_cancelled_exception.
*
* @return *this
*/
task& cancel() & noexcept {
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
return *this;
}
/**
* @brief Cancel the task, it will stop the next time it uses co_await. On co_await-ing this task, throws dpp::task_cancelled_exception.
*
* @return *this
*/
task&& cancel() && noexcept {
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
return *this;
}
};
namespace detail::task {
/**
* @brief Awaitable returned from task::promise_t's final_suspend. Resumes the parent and cleans up its handle if needed
*/
template <typename R>
struct final_awaiter {
/**
* @brief Always suspend at the end of the task. This allows us to clean up and resume the parent
*/
[[nodiscard]] bool await_ready() const noexcept {
return (false);
}
/**
* @brief The suspension logic of the coroutine when it finishes. Always suspend the caller, meaning cleaning up the handle is on us
*
* @param handle The handle of this coroutine
* @return std::coroutine_handle<> Handle to resume, which is either the parent if present or std::noop_coroutine() otherwise
*/
[[nodiscard]] std_coroutine::coroutine_handle<> await_suspend(handle_t<R> handle) const noexcept;
/**
* @brief Function called when this object is co_awaited by the standard library at the end of final_suspend. Do nothing, return nothing
*/
void await_resume() const noexcept {}
};
/**
* @brief Base implementation of task::promise_t, without the logic that would depend on the return type. Meant to be inherited from
*/
template <typename R>
struct promise_base : basic_promise<R> {
/**
* @brief Whether the task is cancelled or not.
*/
std::atomic<bool> cancelled = false;
#ifdef DPP_CORO_TEST
promise_base() {
++coro_alloc_count<promise_base>;
}
~promise_base() {
--coro_alloc_count<promise_base>;
}
#endif
/**
* @brief Function called by the standard library when the coroutine is created.
*
* @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_never">std::suspend_never</a> Don't suspend, the coroutine starts immediately.
*/
std_coroutine::suspend_never initial_suspend() const noexcept {
return {};
}
/**
* @brief Function called by the standard library when an exception is thrown and not caught in the coroutine.
*
* Stores the exception pointer to rethrow on co_await. If the task object is destroyed and was not cancelled, throw instead
*/
void unhandled_exception() {
if ((this->state.load() & promise::state_flags::sf_broken) && !cancelled) {
throw;
}
this->template set_exception<false>(std::current_exception());
}
/**
* @brief Proxy awaitable that wraps any co_await inside the task and checks for cancellation on resumption
*
* @see await_transform
*/
template <typename A>
struct proxy_awaiter {
/** @brief The promise_t object bound to this proxy */
const promise_base &promise;
/** @brief The inner awaitable being awaited */
A awaitable;
/** @brief Wrapper for the awaitable's await_ready */
[[nodiscard]] bool await_ready() noexcept(noexcept(awaitable.await_ready())) {
return awaitable.await_ready();
}
/** @brief Wrapper for the awaitable's await_suspend */
template <typename T>
[[nodiscard]] decltype(auto) await_suspend(T&& handle) noexcept(noexcept(awaitable.await_suspend(std::forward<T>(handle)))) {
return awaitable.await_suspend(std::forward<T>(handle));
}
/**
* @brief Wrapper for the awaitable's await_resume, throws if the task is cancelled
*
* @throw dpp::task_cancelled_exception If the task was cancelled
*/
decltype(auto) await_resume() {
if (promise.cancelled.load()) {
throw dpp::task_cancelled_exception{"task was cancelled"};
}
return awaitable.await_resume();
}
};
/**
* @brief Function called whenever co_await is used inside of the task
*
* @throw dpp::task_cancelled_exception On resumption if the task was cancelled
*
* @return @ref proxy_awaiter Returns a proxy awaiter that will check for cancellation on resumption
*/
template <awaitable_type T>
[[nodiscard]] auto await_transform(T&& expr) const noexcept(noexcept(co_await_resolve(std::forward<T>(expr)))) {
using awaitable_t = decltype(co_await_resolve(std::forward<T>(expr)));
return proxy_awaiter<awaitable_t>{*this, co_await_resolve(std::forward<T>(expr))};
}
};
/**
* @brief Implementation of task::promise_t for non-void return type
*/
template <typename R>
struct promise_t : promise_base<R> {
friend struct final_awaiter<R>;
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
void return_value(R&& expr) noexcept(std::is_nothrow_move_constructible_v<R>) requires std::move_constructible<R> {
this->template set_value<false>(std::move(expr));
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
void return_value(const R &expr) noexcept(std::is_nothrow_copy_constructible_v<R>) requires std::copy_constructible<R> {
this->template set_value<false>(expr);
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
template <typename T>
requires (!std::is_same_v<R, std::remove_cvref_t<T>> && std::convertible_to<T, R>)
void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v<T, R>) {
this->template emplace_value<false>(std::forward<T>(expr));
}
/**
* @brief Function called by the standard library when the coroutine is created.
*
* @return dpp::task The coroutine object
*/
[[nodiscard]] dpp::task<R> get_return_object() noexcept {
return dpp::task<R>{handle_t<R>::from_promise(*this)};
}
/**
* @brief Function called by the standard library when the coroutine reaches its last suspension point
*
* @return final_awaiter Special object containing the chain resolution and clean-up logic.
*/
[[nodiscard]] final_awaiter<R> final_suspend() const noexcept {
return {};
}
};
/**
* @brief Implementation of task::promise_t for void return type
*/
template <>
struct promise_t<void> : promise_base<void> {
friend struct final_awaiter<void>;
/**
* @brief Function called by the standard library when the coroutine co_returns
*
* Sets the promise state to finished.
*/
void return_void() noexcept {
set_value<false>();
}
/**
* @brief Function called by the standard library when the coroutine is created.
*
* @return task The coroutine object
*/
[[nodiscard]] dpp::task<void> get_return_object() noexcept {
return dpp::task<void>{handle_t<void>::from_promise(*this)};
}
/**
* @brief Function called by the standard library when the coroutine reaches its last suspension point
*
* @return final_awaiter Special object containing the chain resolution and clean-up logic.
*/
[[nodiscard]] final_awaiter<void> final_suspend() const noexcept {
return {};
}
};
template <typename R>
std_coroutine::coroutine_handle<> final_awaiter<R>::await_suspend(handle_t<R> handle) const noexcept {
using state_flags = promise::state_flags;
promise_t<R> &promise = handle.promise();
uint8_t previous_state = promise.state.fetch_or(state_flags::sf_done);
if ((previous_state & state_flags::sf_awaited) != 0) { // co_await-ed, resume parent
if ((previous_state & state_flags::sf_broken) != 0) { // major bug, these should never be set together
// we don't have a cluster so just log it on cerr
std::cerr << "dpp: task promise ended in both an awaited and dangling state. this is a bug and a memory leak, please report it to us!" << std::endl;
}
return promise.release_awaiter();
}
if ((previous_state & state_flags::sf_broken) != 0) { // task object is gone, free the handle
handle.destroy();
}
return std_coroutine::noop_coroutine();
}
} // namespace detail::task
DPP_CHECK_ABI_COMPAT(task<void>, task_dummy)
DPP_CHECK_ABI_COMPAT(task<uint64_t>, task_dummy)
}
/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise_t type from a coroutine function.
*/
template<typename T, typename... Args>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::task<T>, Args...> {
using promise_type = dpp::detail::task::promise_t<T>;
};
#endif /* DPP_NO_CORO */

536
include/dpp/coro/when_any.h Normal file
View File

@ -0,0 +1,536 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#ifndef DPP_NO_CORO
#pragma once
#include "coro.h"
#include "job.h"
#include <atomic>
#include <array>
#include <memory>
#include <limits>
#include <optional>
namespace dpp {
template <typename T>
class event_router_t;
namespace detail {
namespace event_router {
template <typename T>
class awaitable;
}
/**
* @brief Internal cogwheels for dpp::when_any
*/
namespace when_any {
/**
* @brief Current state of a when_any object
*/
enum await_state : uint8_t {
/**
* @brief Object is being awaited
*/
waiting = 1 << 0,
/**
* @brief Object was resumed
*/
done = 1 << 1,
/**
* @brief Result is ready to retrieve
*/
ready = 1 << 2,
/**
* @brief Object was destroyed
*/
dangling = 1 << 3
};
/**
* @brief Type trait helper to obtain the actual type that will be used by a when_any when a type is passed as a parameter.
* May specialize for certain types for specific behavior, e.g. for an event_router, store the awaitable directly
*/
template <typename T>
struct arg_helper_s {
/** Raw type of the awaitable */
using type = T;
/** Helper static method to get the awaitable from a variable */
static decltype(auto) get(auto&& v) {
return static_cast<decltype(v)>(v);
}
};
template <typename T>
struct arg_helper_s<dpp::event_router_t<T>> {
using type = event_router::awaitable<T>;
template <typename U>
#ifndef _DOXYGEN
requires (std::same_as<std::remove_cvref_t<U>, dpp::event_router_t<T>>)
#endif
static event_router::awaitable<T> get(U&& v) {
return static_cast<U>(v).operator co_await();
}
};
/**
* @brief Alias for the actual type that an awaitable will be stored as in a when_any.
* For example if given an event_router, store the awaitable, not the event_router.
*/
template <typename T>
using awaitable_type = typename arg_helper_s<T>::type;
/**
* @brief Helper struct with a method to convert an awaitable parameter to the actual value it will be stored as.
* For example if given an event_router, store the awaitable, not the event_router.
*/
template <typename T>
using arg_helper = arg_helper_s<std::remove_cvref_t<T>>;
/**
* @brief Empty result from void-returning awaitable
*/
struct empty{};
/**
* @brief Actual type a result will be stores as in when_any
*/
template <typename T>
using storage_type = std::conditional_t<std::is_void_v<T>, empty, T>;
/**
* @brief Concept satisfied if a stored result is void
*/
template <typename T>
concept void_result = std::same_as<T, empty>;
}
} // namespace detail
/**
* @class when_any when_any.h coro/when_any.h
* @brief Experimental class to co_await on a bunch of awaitable objects, resuming when the first one completes.
* On completion, returns a @ref result object that contains the index of the awaitable that finished first.
* A user can call @ref result::index() and @ref result::get<N>() on the result object to get the result, similar to std::variant.
*
* @see when_any::result
* @tparam Args... Type of each awaitable to await on
*/
template <typename... Args>
#ifndef _DOXYGEN_
requires (sizeof...(Args) >= 1)
#endif
class when_any {
/**
* @brief Alias for the type of the result variant
*/
using variant_type = std::variant<std::exception_ptr, std::remove_cvref_t<detail::when_any::storage_type<detail::awaitable_result<Args>>>...>;
/**
* @brief Alias for the result type of the Nth arg.
*
* @tparam N index of the argument to fetch
*/
template <size_t N>
using result_t = std::variant_alternative_t<N + 1, variant_type>;
/**
* @brief State shared between all the jobs to spawn
*/
struct state_t {
/**
* @brief Constructor for the internal state. Its arguments are used to construct each awaitable
*/
template <typename... Args_>
state_t(Args_&&... args) : awaitables{std::forward<Args_>(args)...} {}
/**
* @brief Awaitable objects to handle.
*/
std::tuple<Args...> awaitables;
/**
* @brief Result or exception, as a variant. This will contain the result of the first awaitable to finish
*/
variant_type result{};
/**
* @brief Coroutine handle to resume after finishing an awaitable
*/
detail::std_coroutine::coroutine_handle<> handle{};
/**
* @brief Index of the awaitable that finished. Initialized to the maximum value of std::size_t.
*/
size_t index_finished = std::numeric_limits<std::size_t>::max();
/**
* @brief State of the when_any object.
*
* @see detail::when_any::await_state
*/
std::atomic<uint8_t> owner_state{};
};
/**
* @brief Shared pointer to the state shared between the jobs spawned. Contains the awaitable objects and the result.
*/
std::shared_ptr<state_t> my_state{nullptr};
/**
* @brief Spawn a dpp::job handling the Nth argument.
*
* @tparam N Index of the argument to handle
* @return dpp::job Job handling the Nth argument
*/
template <size_t N>
static job make_job(std::shared_ptr<state_t> shared_state) {
using namespace detail::when_any;
/**
* Any exceptions from the awaitable's await_suspend should be thrown to the caller (the coroutine creating the when_any object)
* If the co_await passes, and it is the first one to complete, try construct the result, catch any exceptions to rethrow at resumption, and resume.
*/
if constexpr (!std::same_as<result_t<N>, empty>) {
decltype(auto) result = co_await std::get<N>(shared_state->awaitables);
if (auto s = shared_state->owner_state.fetch_or(await_state::done, std::memory_order_relaxed); (s & (await_state::done | await_state::dangling)) != 0) {
co_return;
}
using result_t = decltype(result);
/* Try construct, prefer move if possible, store any exception to rethrow */
try {
if constexpr (std::is_lvalue_reference_v<result_t> && !std::is_const_v<result_t> && std::is_move_constructible_v<std::remove_cvref_t<result_t>>) {
shared_state->result.template emplace<N + 1>(std::move(result));
} else {
shared_state->result.template emplace<N + 1>(result);
}
} catch (...) {
shared_state->result.template emplace<0>(std::current_exception());
}
} else {
co_await std::get<N>(shared_state->awaitables);
if (auto s = shared_state->owner_state.fetch_or(await_state::done, std::memory_order_relaxed); (s & (await_state::done | await_state::dangling)) != 0) {
co_return;
}
shared_state->result.template emplace<N + 1>();
}
shared_state->index_finished = N;
if (auto s = shared_state->owner_state.fetch_or(await_state::ready, std::memory_order_acq_rel); (s & (await_state::waiting)) != 0) {
assert(shared_state->handle);
shared_state->handle.resume();
}
}
/**
* @brief Spawn a dpp::job to handle each awaitable.
* Each of them will co_await the awaitable and set the result if they are the first to finish
*/
void make_jobs() {
constexpr auto impl = []<size_t... Ns>(when_any *self, std::index_sequence<Ns...>) {
// We create an array to guarantee evaluation order here
// https://eel.is/c++draft/dcl.init.aggr#7
[[maybe_unused]] dpp::job jobs[] = { make_job<Ns>(self->my_state)... };
};
impl(this, std::index_sequence_for<Args...>{});
}
public:
/**
* @brief Object returned by \ref operator co_await() on resumption. Can be moved but not copied.
*/
class result {
friend class when_any<Args...>;
/**
* @brief Reference to the shared state to pull the data from
*/
std::shared_ptr<state_t> shared_state;
/**
* @brief Default construction is deleted
*/
result() = delete;
/**
* @brief Internal constructor taking the shared state
*/
result(std::shared_ptr<state_t> state) : shared_state{state} {}
public:
/**
* @brief Move constructor
*/
result(result&&) = default;
/**
* @brief This object is not copyable.
*/
result(const result &) = delete;
/**
* @brief Move assignment operator
*/
result &operator=(result&&) = default;
/**
* @brief This object is not copyable.
*/
result &operator=(const result&) = delete;
/**
* @brief Retrieve the index of the awaitable that finished first.
*
* @return size_t Index of the awaitable that finished first, relative to the template arguments of when_any
*/
size_t index() const noexcept {
return shared_state->index_finished;
}
/**
* @brief Retrieve the non-void result of an awaitable.
*
* @tparam N Index of the result to retrieve. Must correspond to index().
* @throw ??? Throws any exception triggered at construction, or std::bad_variant_access if N does not correspond to index()
* @return Result of the awaitable as a reference.
*/
template <size_t N>
#ifndef _DOXYGEN_
requires (!detail::when_any::void_result<result_t<N>>)
#endif
result_t<N>& get() & {
if (is_exception()) {
std::rethrow_exception(std::get<0>(shared_state->result));
}
return std::get<N + 1>(shared_state->result);
}
/**
* @brief Retrieve the non-void result of an awaitable.
*
* @tparam N Index of the result to retrieve. Must correspond to index().
* @throw ??? Throws any exception triggered at construction, or std::bad_variant_access if N does not correspond to index()
* @return Result of the awaitable as a cpnst reference.
*/
template <size_t N>
#ifndef _DOXYGEN_
requires (!detail::when_any::void_result<result_t<N>>)
#endif
const result_t<N>& get() const& {
if (is_exception()) {
std::rethrow_exception(std::get<0>(shared_state->result));
}
return std::get<N + 1>(shared_state->result);
}
/**
* @brief Retrieve the non-void result of an awaitable.
*
* @tparam N Index of the result to retrieve. Must correspond to index().
* @throw ??? Throws any exception triggered at construction, or std::bad_variant_access if N does not correspond to index()
* @return Result of the awaitable as an rvalue reference.
*/
template <size_t N>
#ifndef _DOXYGEN_
requires (!detail::when_any::void_result<result_t<N>>)
#endif
result_t<N>&& get() && {
if (is_exception()) {
std::rethrow_exception(std::get<0>(shared_state->result));
}
return std::get<N + 1>(shared_state->result);
}
/**
* @brief Cannot retrieve a void result.
*/
template <size_t N>
#ifndef _DOXYGEN
requires (detail::when_any::void_result<result_t<N>>)
#endif
[[deprecated("cannot retrieve a void result")]] void get() = delete;
/**
* @brief Checks whether the return of the first awaitable triggered an exception, that is, a call to get() will rethrow.
*
* @return Whether or not the result is an exception
*/
[[nodiscard]] bool is_exception() const noexcept {
return shared_state->result.index() == 0;
}
};
/**
* @brief Object returned by \ref operator co_await(). Meant to be used by the standard library, not by a user.
*
* @see result
*/
struct awaiter {
/**
* @brief Pointer to the when_any object
*/
when_any *self;
/**
* @brief First function called by the standard library when using co_await.
*
* @return bool Whether the result is ready
*/
[[nodiscard]] bool await_ready() const noexcept {
return self->await_ready();
}
/**
* @brief Second function called by the standard library when using co_await.
*
* @return bool Returns false if we want to resume immediately.
*/
bool await_suspend(detail::std_coroutine::coroutine_handle<> caller) noexcept {
using namespace detail::when_any;
self->my_state->handle = caller;
auto prev = self->my_state->owner_state.fetch_or(await_state::waiting, std::memory_order_acq_rel);
return (prev & await_state::ready) == 0; // true (suspend) if the state was not `ready` -- false (resume) if it was
}
/**
* @brief Third and final function called by the standard library when using co_await. Returns the result object.
*
* @see result
*/
result await_resume() const noexcept {
return { self->my_state };
}
};
/**
* @brief Default constructor.
* A when_any object created this way holds no state
*/
when_any() = default;
/**
* @brief Constructor from awaitable objects. Each awaitable is executed immediately and the when_any object can then be co_await-ed later.
*
* @throw ??? Any exception thrown by the start of each awaitable will propagate to the caller.
* @param args Arguments to construct each awaitable from. The when_any object will construct an awaitable for each, it is recommended to pass rvalues or std::move.
*/
template <typename... Args_>
#ifndef _DOXYGEN_
requires (sizeof...(Args_) == sizeof...(Args))
#endif /* _DOXYGEN_ */
when_any(Args_&&... args) : my_state{ std::make_shared<state_t>(detail::when_any::arg_helper<Args_>::get(std::forward<Args_>(args))...) } {
make_jobs();
}
/**
* @brief This object is not copyable.
*/
when_any(const when_any &) = delete;
/**
* @brief Move constructor.
*/
when_any(when_any &&) noexcept = default;
/**
* @brief On destruction the when_any will try to call @ref dpp::task::cancel() cancel() on each of its awaitable if they have such a method.
*
* @note If you are looking to use a custom type with when_any and want it to cancel on its destruction,
* make sure it has a cancel() method, which will trigger an await_resume() throwing a dpp::task_cancelled_exception.
* This object will swallow the exception and return cleanly. Any other exception will be thrown back to the resumer.
*/
~when_any() {
if (!my_state)
return;
my_state->owner_state = detail::when_any::await_state::dangling;
[]<size_t... Ns>(when_any *self, std::index_sequence<Ns...>) constexpr {
constexpr auto cancel = []<size_t N>(when_any *self) constexpr {
if constexpr (requires { std::get<N>(self->my_state->awaitables).cancel(); }) {
try {
std::get<N>(self->my_state->awaitables).cancel();
} catch (...) {
// swallow any exception. no choice here, we're in a destructor
}
}
};
(cancel.template operator()<Ns>(self), ...);
}(this, std::index_sequence_for<Args...>());
}
/**
* @brief This object is not copyable.
*/
when_any &operator=(const when_any &) = delete;
/**
* @brief Move assignment operator.
*/
when_any &operator=(when_any &&) noexcept = default;
/**
* @brief Check whether a call to co_await would suspend.
*
* @note This can change from false to true at any point, but not the other way around.
* @return bool Whether co_await would suspend
*/
[[nodiscard]] bool await_ready() const noexcept {
return (my_state->owner_state.load(std::memory_order_acquire) & detail::when_any::await_state::ready) != 0;
}
/**
* @brief Suspend the caller until any of the awaitables completes.
*
* @see result
* @throw ??? On resumption, throws any exception caused by the construction of the result.
* @return result On resumption, this object returns an object that allows to retrieve the index and result of the awaitable.
*/
[[nodiscard]] awaiter operator co_await() noexcept {
return { this };
}
};
template <typename... Args>
#ifndef _DOXYGEN_
requires (sizeof...(Args) >= 1)
#endif /* _DOXYGEN_ */
when_any(Args...) -> when_any<detail::when_any::awaitable_type<Args>...>;
} /* namespace dpp */
#endif

View File

@ -0,0 +1,74 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/http_server.h>
#include <dpp/signature_verifier.h>
namespace dpp {
/**
* @brief Creates a HTTP server which listens for incoming
* Discord interactions, and if verified as valid, raises them
* as cluster events, returning the response back.
* Note that Discord requires all interaction endpoints to
* have a valid SSL certificate (not self signed) so in most
* cases you should put this port behind a reverse proxy, e.g.
* nginx, apache, etc.
*/
struct discord_webhook_server : public http_server {
/**
* @brief Verifier for signed requests
*/
signature_verifier verifier;
/**
* @brief Public key from application dashboard
*/
std::string public_key_hex;
/**
* @brief Constructor for creation of a HTTP(S) server
* @param creator Cluster creator
* @param discord_public_key Public key for the application from the application dashboard page
* @param address address to bind to, use "0.0.0.0" to bind to all local addresses
* @param port port to bind to. You should generally use a port > 1024.
* @param ssl_private_key Private key PEM file for HTTPS/SSL. If empty, a plaintext server is created
* @param ssl_public_key Public key PEM file for HTTPS/SSL. If empty, a plaintext server is created
*/
discord_webhook_server(cluster* creator, const std::string& discord_public_key, const std::string_view address, uint16_t port, const std::string& ssl_private_key = "", const std::string& ssl_public_key = "");
/**
* @brief Handle Discord outbound webhook
* @param request Request from discord
*/
void handle_request(http_server_request* request);
/**
* @brief Virtual dtor
*/
virtual ~discord_webhook_server() = default;
};
}

624
include/dpp/discordclient.h Normal file
View File

@ -0,0 +1,624 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <map>
#include <vector>
#include <dpp/json_fwd.h>
#include <dpp/wsclient.h>
#include <dpp/dispatcher.h>
#include <dpp/event.h>
#include <queue>
#include <thread>
#include <memory>
#include <deque>
#include <dpp/etf.h>
#include <mutex>
#include <shared_mutex>
#include <dpp/zlibcontext.h>
namespace dpp {
/**
* @brief Discord API version for shard websockets and HTTPS API requests
*/
#define DISCORD_API_VERSION "10"
/**
* @brief HTTPS Request base path for API calls
*/
#define API_PATH "/api/v" DISCORD_API_VERSION
/* Forward declarations */
class cluster;
/**
* @brief How many seconds to wait between (re)connections. DO NOT change this.
* It is mandated by the Discord API spec!
*/
constexpr time_t RECONNECT_INTERVAL = 5;
/**
* @brief Represents different event opcodes sent and received on a shard websocket
*
* These are used internally to route frames.
*/
enum shard_frame_type : int {
/**
* @brief An event was dispatched.
* @note Receive only
*/
ft_dispatch = 0,
/**
* @brief Fired periodically by the client to keep the connection alive.
* @note Send/Receive
*/
ft_heartbeat = 1,
/**
* @brief Starts a new session during the initial handshake.
* @note Send only
*/
ft_identify = 2,
/**
* @brief Update the client's presence.
* @note Send only
*/
ft_presence = 3,
/**
* @brief Used to join/leave or move between voice channels.
* @note Send only
*/
ft_voice_state_update = 4,
/**
* @brief Resume a previous session that was disconnected.
* @note Send only
*/
ft_resume = 6,
/**
* @brief You should attempt to reconnect and resume immediately.
* @note Receive only
*/
ft_reconnect = 7,
/**
* @brief Request information about offline guild members in a large guild.
* @note Send only
*/
ft_request_guild_members = 8,
/**
* @brief The session has been invalidated. You should reconnect and identify/resume accordingly.
* @note Receive only
*/
ft_invalid_session = 9,
/**
* @brief Sent immediately after connecting, contains the heartbeat interval to use.
* @note Receive only
*/
ft_hello = 10,
/**
* @brief Sent in response to receiving a heartbeat to acknowledge that it has been received.
* @note Receive only
*/
ft_heartbeat_ack = 11,
/**
* @brief Request information about soundboard sounds in a set of guilds.
* @note Send only
*/
ft_request_soundboard_sounds = 31,
};
/**
* @brief Represents a connection to a voice channel.
* A client can only connect to one voice channel per guild at a time, so these are stored in a map
* in the dpp::discord_client keyed by guild_id.
*/
class DPP_EXPORT voiceconn {
/**
* @brief Owning dpp::discord_client instance
*/
class discord_client* creator;
public:
/**
* @brief Voice Channel ID
*/
snowflake channel_id;
/**
* @brief Websocket hostname for status
*/
std::string websocket_hostname;
/**
* @brief Voice Voice session ID
*/
std::string session_id;
/**
* @brief Voice websocket token
*/
std::string token;
/**
* @brief voice websocket client
*/
class discord_voice_client* voiceclient;
/**
* @brief True to enable DAVE E2EE
* @warning This is an EXPERIMENTAL feature!
*/
bool dave;
/**
* @brief Construct a new voiceconn object
*/
voiceconn() = default;
/**
* @brief Construct a new voiceconn object
*
* @param o owner
* @param _channel_id voice channel id
* @param enable_dave True to enable DAVE E2EE
* @warn DAVE is an EXPERIMENTAL feature!
*/
voiceconn(class discord_client* o, snowflake _channel_id, bool enable_dave);
/**
* @brief Destroy the voiceconn object
*/
~voiceconn();
/**
* @brief return true if the connection is ready to connect
* (has hostname, token and session id)
*
* @return true if ready to connect
*/
bool is_ready() const;
/**
* @brief return true if the connection is active (websocket exists)
*
* @return true if has an active websocket
*/
bool is_active() const;
/**
* @brief Create websocket object and connect it.
* Needs hostname, token and session_id to be set or does nothing.
*
* @param guild_id Guild to connect to the voice channel on
* @return reference to self
* @note It can spawn a thread to establish the connection, so this is NOT a synchronous blocking call!
* You shouldn't call this directly. Use a wrapper function instead. e.g. dpp::guild::connect_member_voice
*/
voiceconn& connect(snowflake guild_id);
/**
* @brief Disconnect from the currently connected voice channel
* @return reference to self
*/
voiceconn& disconnect();
/**
* @brief Reassigns the owner to the given discord_client.
*/
void reassign_owner(class discord_client* o);
};
/** @brief Implements a discord client. Each discord_client connects to one shard and derives from a websocket client. */
class DPP_EXPORT discord_client : public websocket_client
{
protected:
/**
* @brief Needed so that voice_state_update can call dpp::discord_client::disconnect_voice_internal
*/
friend class dpp::events::voice_state_update;
/**
* @brief Needed so that guild_create can request member chunks if you have the correct intents
*/
friend class dpp::events::guild_create;
/**
* @brief Needed to allow cluster::set_presence to use the ETF functions
*/
friend class dpp::cluster;
/**
* @brief Disconnect from the connected voice channel on a guild
*
* @param guild_id The guild who's voice channel you wish to disconnect from
* @param send_json True if we should send a json message confirming we are leaving the VC
* Should be set to false if we already receive this message in an event.
*/
void disconnect_voice_internal(snowflake guild_id, bool send_json = true);
/**
* @brief Start connecting the websocket
*
* Called from the constructor, or during reconnection
*/
void start_connecting();
/**
* @brief Stores the most recent ping message on this shard, which we check
* for to monitor latency
*/
std::string last_ping_message;
private:
/**
* @brief Mutex for message queue
*/
std::shared_mutex queue_mutex;
/**
* @brief Mutex for zlib pointer
*/
std::mutex zlib_mutex;
/**
* @brief Queue of outbound messages
*/
std::deque<std::string> message_queue;
/**
* @brief If true, stream compression is enabled
*/
bool compressed;
/**
* @brief Decompressed string
*/
std::string decompressed;
/**
* @brief This object contains the various zlib structs which
* are not usable by the user of the library directly. They
* are wrapped within this opaque object so that this header
* file does not bring in a dependency on zlib.h.
*/
std::unique_ptr<zlibcontext> zlib{};
/**
* @brief Last connect time of cluster
*/
time_t connect_time;
/**
* @brief Time last ping sent to websocket, in fractional seconds
*/
double ping_start;
/**
* @brief ETF parser for when in ws_etf mode
*/
std::unique_ptr<etf_parser> etf;
/**
* @brief Convert a JSON object to string.
* In JSON protocol mode, call json.dump(), and in ETF mode,
* call etf::build().
*
* @param json nlohmann::json object to convert
* @return std::string string output in the correct format
*/
std::string jsonobj_to_string(const nlohmann::json& json);
/**
* @brief Update the websocket hostname with the resume url
* from the last READY event
*/
void set_resume_hostname();
/**
* @brief Clean up resources
*/
void cleanup();
public:
/**
* @brief Owning cluster
*/
class dpp::cluster* creator;
/**
* @brief Heartbeat interval for sending heartbeat keepalive
* @note value in milliseconds
*/
uint32_t heartbeat_interval;
/**
* @brief Last heartbeat
*/
time_t last_heartbeat;
/**
* @brief Shard ID of this client
*/
uint32_t shard_id;
/**
* @brief Total number of shards
*/
uint32_t max_shards;
/**
* @brief Last sequence number received, for resumes and pings
*/
uint64_t last_seq;
/**
* @brief Discord bot token
*/
std::string token;
/**
* @brief Privileged gateway intents
* @see dpp::intents
*/
uint32_t intents;
/**
* @brief Discord session id
*/
std::string sessionid;
/**
* @brief Mutex for voice connections map
*/
std::shared_mutex voice_mutex;
/**
* @brief Resume count
*/
uint32_t resumes;
/**
* @brief Reconnection count
*/
uint32_t reconnects;
/**
* @brief Websocket latency in fractional seconds
*/
double websocket_ping;
/**
* @brief True if READY or RESUMED has been received
*/
bool ready;
/**
* @brief Last heartbeat ACK (opcode 11)
*/
time_t last_heartbeat_ack;
/**
* @brief Current websocket protocol, currently either ETF or JSON
*/
websocket_protocol_t protocol;
/**
* @brief List of voice channels we are connecting to keyed by guild id
*/
std::unordered_map<snowflake, std::unique_ptr<voiceconn>> connecting_voice_channels;
/**
* @brief The gateway address we reconnect to when we resume a session
*/
std::string resume_gateway_url;
/**
* @brief Log a message to whatever log the user is using.
* The logged message is passed up the chain to the on_log event in user code which can then do whatever
* it wants to do with it.
* @param severity The log level from dpp::loglevel
* @param msg The log message to output
*/
virtual void log(dpp::loglevel severity, const std::string &msg) const override;
/**
* @brief Handle an event (opcode 0)
* @param event Event name, e.g. MESSAGE_CREATE
* @param j JSON object for the event content
* @param raw Raw JSON event string
*/
virtual void handle_event(const std::string &event, json &j, const std::string &raw);
/**
* @brief Get the Guild Count for this shard
*
* @return uint64_t guild count
*/
uint64_t get_guild_count();
/**
* @brief Get the Member Count for this shard
*
* @return uint64_t member count
*/
uint64_t get_member_count();
/**
* @brief Get the Channel Count for this shard
*
* @return uint64_t channel count
*/
uint64_t get_channel_count();
/**
* @brief Fires every second from the underlying socket I/O loop, used for sending heartbeats
* and any queued outbound websocket frames.
*/
virtual void one_second_timer() override;
/**
* @brief Queue a message to be sent via the websocket
*
* @param j The JSON data of the message to be sent
* @param to_front If set to true, will place the message at the front of the queue not the back
* (this is for urgent messages such as heartbeat, presence, so they can take precedence over
* chunk requests etc)
*/
void queue_message(const std::string &j, bool to_front = false);
/**
* @brief Clear the outbound message queue
* @return reference to self
*/
discord_client& clear_queue();
/**
* @brief Get the size of the outbound message queue
*
* @return The size of the queue
*/
size_t get_queue_size();
/**
* @brief Returns true if the shard is connected
*
* @return True if connected
*/
bool is_connected();
/**
* @brief Returns the connection time of the shard
*
* @return dpp::utility::uptime Detail of how long the shard has been connected for
*/
dpp::utility::uptime get_uptime();
/**
* @brief Construct a new discord_client object
*
* @param _cluster The owning cluster for this shard
* @param _shard_id The ID of the shard to start
* @param _max_shards The total number of shards across all clusters
* @param _token The bot token to use for identifying to the websocket
* @param intents Privileged intents to use, a bitmask of values from dpp::intents
* @param compressed True if the received data will be gzip compressed
* @param ws_protocol Websocket protocol to use for the connection, JSON or ETF
*
* @throws std::bad_alloc Passed up to the caller if any internal objects fail to allocate, after cleanup has completed
*/
discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t intents = 0, bool compressed = true, websocket_protocol_t ws_protocol = ws_json);
/**
* @brief Construct a discord_client object from another discord_client object
* Used when resuming, the url to connect to will be taken from the resume url of the
* other object, along with the seq number.
*
* @param old Previous connection to resume from
* @param sequence Sequence number of previous session
* @param session_id Session ID of previous session
*/
explicit discord_client(discord_client& old, uint64_t sequence, const std::string& session_id);
/**
* @brief Destroy the discord client object
*/
virtual ~discord_client() = default;
/**
* @brief Get decompressed total bytes received
*
* This will always return 0 if the connection is not compressed
* @return uint64_t compressed bytes received
*/
uint64_t get_decompressed_bytes_in();
/**
* @brief Handle JSON from the websocket.
* @param buffer The entire buffer content from the websocket client
* @param opcode The type of frame, e.g. text or binary
* @returns True if a frame has been handled
*/
virtual bool handle_frame(const std::string &buffer, ws_opcode opcode) override;
/**
* @brief Handle a websocket error.
* @param errorcode The error returned from the websocket
*/
virtual void error(uint32_t errorcode) override;
/**
* @brief Start and monitor I/O loop.
*/
void run();
/**
* @brief Called when the HTTP socket is closed
*/
virtual void on_disconnect() override;
/**
* @brief Connect to a voice channel
*
* @param guild_id Guild where the voice channel is
* @param channel_id Channel ID of the voice channel
* @param self_mute True if the bot should mute itself
* @param self_deaf True if the bot should deafen itself
* @param enable_dave True to enable DAVE E2EE - EXPERIMENTAL
* @return reference to self
* @note This is NOT a synchronous blocking call! The bot isn't instantly ready to send or listen for audio,
* as we have to wait for the connection to the voice server to be established!
* e.g. wait for dpp::cluster::on_voice_ready event, and then send the audio within that event.
*/
discord_client& connect_voice(snowflake guild_id, snowflake channel_id, bool self_mute = false, bool self_deaf = false, bool enable_dave = false);
/**
* @brief Disconnect from the connected voice channel on a guild
*
* @param guild_id The guild who's voice channel you wish to disconnect from
* @return reference to self
* @note This is NOT a synchronous blocking call! The bot isn't instantly disconnected.
*/
discord_client& disconnect_voice(snowflake guild_id);
/**
* @brief Get the dpp::voiceconn object for a specific guild on this shard.
*
* @param guild_id The guild ID to retrieve the voice connection for
* @return voiceconn* The voice connection for the guild, or nullptr if there is no
* voice connection to this guild.
*/
voiceconn* get_voice(snowflake guild_id);
};
}

232
include/dpp/discordevents.h Normal file
View File

@ -0,0 +1,232 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
#include <dpp/utility.h>
#include <string_view>
#include <functional>
namespace dpp {
/**
* @brief Returns a snowflake id from a json field value, if defined, else returns 0
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
uint64_t DPP_EXPORT snowflake_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets a snowflake id from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_snowflake_not_null(const nlohmann::json* j, const char *keyname, uint64_t &v);
/**
* @brief Sets an array of snowflakes from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for the values
* @param v Value to change
*/
void DPP_EXPORT set_snowflake_array_not_null(const nlohmann::json* j, const char *keyname, std::vector<class snowflake> &v);
/**
* @brief Applies a function to each element of a json array.
* @param parent nlohmann::json instance to retrieve value from
* @param key key name to check for the values
* @param fn function to apply to each element
*/
void DPP_EXPORT for_each_json(nlohmann::json* parent, std::string_view key, const std::function<void(nlohmann::json*)> &fn);
/**
* @brief Sets an array of objects from a json field value, if defined, else does nothing
* @tparam T The class of which the array consists of. Must be derived from dpp::json_interface
* @param j nlohmann::json instance to retrieve value from
* @param key key name to check for the values
* @param v Value to change
*/
template<class T> void set_object_array_not_null(nlohmann::json* j, std::string_view key, std::vector<T>& v) {
v.clear();
for_each_json(j, key, [&v](nlohmann::json* elem) {
v.push_back(T{}.fill_from_json(elem));
});
}
/**
* @brief Returns a string from a json field value, if defined, else returns an empty string.
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
std::string DPP_EXPORT string_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets a string from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_string_not_null(const nlohmann::json* j, const char *keyname, std::string &v);
/**
* @brief This is a repeat of set_string_not_null, but takes in a iconhash.
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_iconhash_not_null(const nlohmann::json* j, const char *keyname, utility::iconhash &v);
/**
* @brief Returns a double from a json field value, if defined, else returns 0.
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
double DPP_EXPORT double_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets a double from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_double_not_null(const nlohmann::json* j, const char *keyname, double &v);
/**
* @brief Returns a 64 bit unsigned integer from a json field value, if defined, else returns 0.
* DO NOT use this for snowflakes, as usually snowflakes are wrapped in a string!
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
uint64_t DPP_EXPORT int64_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets an unsigned 64 bit integer from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_int64_not_null(const nlohmann::json* j, const char *keyname, uint64_t &v);
/**
* @brief Returns a 32 bit unsigned integer from a json field value, if defined, else returns 0
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
uint32_t DPP_EXPORT int32_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets an unsigned 32 bit integer from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_int32_not_null(const nlohmann::json* j, const char *keyname, uint32_t &v);
/**
* @brief Returns a 16 bit unsigned integer from a json field value, if defined, else returns 0
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
uint16_t DPP_EXPORT int16_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets an unsigned 16 bit integer from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_int16_not_null(const nlohmann::json* j, const char *keyname, uint16_t &v);
/**
* @brief Returns an 8 bit unsigned integer from a json field value, if defined, else returns 0
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
uint8_t DPP_EXPORT int8_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets an unsigned 8 bit integer from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_int8_not_null(const nlohmann::json* j, const char *keyname, uint8_t &v);
/**
* @brief Returns a boolean value from a json field value, if defined, else returns false
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
bool DPP_EXPORT bool_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets a boolean from a json field value, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_bool_not_null(const nlohmann::json* j, const char *keyname, bool &v);
/**
* @brief Returns a time_t from an ISO8601 timestamp field in a json value, if defined, else returns
* epoch value of 0.
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @return found value
*/
time_t DPP_EXPORT ts_not_null(const nlohmann::json* j, const char *keyname);
/**
* @brief Sets an timestamp from a json field value containing an ISO8601 string, if defined, else does nothing
* @param j nlohmann::json instance to retrieve value from
* @param keyname key name to check for a value
* @param v Value to change
*/
void DPP_EXPORT set_ts_not_null(const nlohmann::json* j, const char *keyname, time_t &v);
/**
* @brief Base64 encode data into a string.
* @param buf Raw binary buffer
* @param buffer_length Buffer length to encode
* @return The base64 encoded string
*/
std::string DPP_EXPORT base64_encode(unsigned char const* buf, unsigned int buffer_length);
/**
* @brief Convert time_t unix epoch to std::string ISO date/time
*
* @param ts Timestamp to convert
* @return std::string Converted time/date string
*/
std::string DPP_EXPORT ts_to_string(time_t ts);
}

File diff suppressed because it is too large Load Diff

2319
include/dpp/dispatcher.h Normal file

File diff suppressed because it is too large Load Diff

98
include/dpp/dns.h Normal file
View File

@ -0,0 +1,98 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#ifdef _WIN32
#include <WinSock2.h>
#include <WS2tcpip.h>
#else
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#endif
#include <sys/types.h>
#include <string>
#include <unordered_map>
#include <cstring>
#include <memory>
#include <dpp/socket.h>
namespace dpp {
/**
* @brief Represents a cached DNS result.
* Used by the ssl_connection class to store cached copies of dns lookups.
*/
struct DPP_EXPORT dns_cache_entry {
/**
* @brief Resolved address metadata
*/
addrinfo addr;
/**
* @brief Resolved address as string.
* The metadata is needed to know what type of address it is.
* Do not do silly stuff like just looking to see if '.' is in it!
*/
std::string resolved_addr;
/**
* @brief Time at which this cache entry is invalidated
*/
time_t expire_timestamp;
/**
* @brief Get address length
* @return address length
*/
[[nodiscard]] int size() const;
/**
* @brief Get the address_t that corresponds to this cache entry
* for use when connecting with ::connect()
* @param port Port number to connect to
* @return address_t prefilled with the IP and port number
*/
[[nodiscard]] const address_t get_connecting_address(uint16_t port) const;
/**
* @brief Allocate a socket file descriptor for the given dns address
* @return File descriptor ready for calling connect(), or INVALID_SOCKET
* on failure.
*/
[[nodiscard]] socket make_connecting_socket() const;
};
/**
* @brief Cache container type
*/
using dns_cache_t = std::unordered_map<std::string, std::unique_ptr<dns_cache_entry>>;
/**
* @brief Resolve a hostname to an addrinfo
*
* @param hostname Hostname to resolve
* @param port A port number or named service, e.g. "80"
* @return dns_cache_entry* First IP address associated with the hostname DNS record
* @throw dpp::connection_exception On failure to resolve hostname
*/
DPP_EXPORT const dns_cache_entry *resolve_hostname(const std::string &hostname, const std::string &port);
}

82
include/dpp/dpp.h Normal file
View File

@ -0,0 +1,82 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/version.h>
#include <string>
#include <map>
#include <vector>
#include <fstream>
#include <iostream>
#include <ctime>
#include <string>
#include <vector>
#include <map>
#include <functional>
#include <dpp/exception.h>
#include <dpp/snowflake.h>
#include <dpp/misc-enum.h>
#include <dpp/stringops.h>
#include <dpp/managed.h>
#include <dpp/socketengine.h>
#include <dpp/utility.h>
#include <dpp/voicestate.h>
#include <dpp/permissions.h>
#include <dpp/role.h>
#include <dpp/user.h>
#include <dpp/channel.h>
#include <dpp/thread.h>
#include <dpp/guild.h>
#include <dpp/invite.h>
#include <dpp/dtemplate.h>
#include <dpp/emoji.h>
#include <dpp/ban.h>
#include <dpp/prune.h>
#include <dpp/voiceregion.h>
#include <dpp/integration.h>
#include <dpp/webhook.h>
#include <dpp/presence.h>
#include <dpp/intents.h>
#include <dpp/message.h>
#include <dpp/appcommand.h>
#include <dpp/stage_instance.h>
#include <dpp/auditlog.h>
#include <dpp/application.h>
#include <dpp/scheduled_event.h>
#include <dpp/discordclient.h>
#include <dpp/dispatcher.h>
#include <dpp/cluster.h>
#include <dpp/cache.h>
#include <dpp/httpsclient.h>
#include <dpp/queues.h>
#include <dpp/commandhandler.h>
#include <dpp/once.h>
#include <dpp/colors.h>
#include <dpp/discordevents.h>
#include <dpp/timed_listener.h>
#include <dpp/collector.h>
#include <dpp/bignum.h>
#include <dpp/thread_pool.h>
#include <dpp/signature_verifier.h>
#include <dpp/socket_listener.h>
#include <dpp/http_server.h>
#include <dpp/discord_webhook_server.h>

115
include/dpp/dtemplate.h Normal file
View File

@ -0,0 +1,115 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
#include <unordered_map>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Represents a guild template
*/
class DPP_EXPORT dtemplate : public json_interface<dtemplate> {
protected:
friend struct json_interface<dtemplate>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
dtemplate& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build the JSON for this object
*
* @param with_id Add ID to output
* @return json JSON content
*/
json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Template code
*/
std::string code;
/**
* @brief Template name
*/
std::string name;
/**
* @brief Template description
*/
std::string description;
/**
* @brief Usage counter
*/
uint32_t usage_count;
/**
* @brief User ID of creator
*/
snowflake creator_id;
/**
* @brief Creation date/time
*
*/
time_t created_at;
/**
* @brief Last update date/time
*/
time_t updated_at;
/**
* @brief Guild id the template is created from
*/
snowflake source_guild_id;
/**
* @brief True if needs synchronising
*/
bool is_dirty;
/**
* @brief Construct a new dtemplate object
*/
dtemplate();
/**
* @brief Destroy the dtemplate object
*/
virtual ~dtemplate() = default;
};
/**
* @brief A container of invites
*/
typedef std::unordered_map<snowflake, dtemplate> dtemplate_map;
}

252
include/dpp/emoji.h Normal file
View File

@ -0,0 +1,252 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/misc-enum.h>
#include <dpp/managed.h>
#include <dpp/utility.h>
#include <dpp/json_fwd.h>
#include <unordered_map>
#include <dpp/json_interface.h>
namespace dpp {
#define MAX_EMOJI_SIZE 256 * 1024
/**
* @brief Flags for dpp::emoji
*/
enum emoji_flags : uint8_t {
/**
* @brief Emoji requires colons.
*/
e_require_colons = 0b00000001,
/**
* @brief Managed (introduced by application)
*/
e_managed = 0b00000010,
/**
* @brief Animated emoji.
*/
e_animated = 0b00000100,
/**
* @brief Available (false if the guild doesn't meet boosting criteria, etc)
*/
e_available = 0b00001000,
};
/**
* @brief Represents an emoji for a dpp::guild
*/
class DPP_EXPORT emoji : public managed, public json_interface<emoji> {
protected:
friend struct json_interface<emoji>;
/**
* @brief Read class values from json object
*
* @param j A json object to read from
* @return A reference to self
*/
emoji& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build the json for this object
*
* @param with_id include the id in the JSON
* @return std::string json data
*/
json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Emoji name.
*/
std::string name{};
/**
* @brief Roles allowed to use this emoji.
*/
std::vector<snowflake> roles;
/**
* @brief The id of the user that created this emoji.
*/
snowflake user_id;
/**
* @brief Image data for the emoji, if uploading.
*/
utility::image_data image_data;
/**
* @brief Flags for the emoji from dpp::emoji_flags.
*/
uint8_t flags{0};
/**
* @brief Construct a new emoji object
*/
emoji() = default;
/**
* @brief Construct a new emoji object with name, ID and flags
*
* @param name The emoji's name
* @param id ID, if it has one (unicode does not)
* @param flags Emoji flags (emoji_flags)
*/
emoji(const std::string_view name, const snowflake id = 0, const uint8_t flags = 0);
/**
* @brief Copy constructor, copies another emoji's data
*
* @param rhs Emoji to copy
*/
emoji(const emoji &rhs) = default;
/**
* @brief Move constructor, moves another emoji's data to this
*
* @param rhs Emoji to move from
*/
emoji(emoji &&rhs) noexcept = default;
/**
* @brief Destroy the emoji object
*/
~emoji() override = default;
/**
* @brief Copy assignment operator, copies another emoji's data
*
* @param rhs Emoji to copy
*/
emoji &operator=(const emoji &rhs) = default;
/**
* @brief Move constructor, moves another emoji's data to this
*
* @param rhs Emoji to move from
*/
emoji &operator=(emoji &&rhs) noexcept = default;
/**
* @brief Create a mentionable emoji
* @param name The name of the emoji.
* @param id The ID of the emoji.
* @param is_animated is emoji animated.
* @return std::string The formatted mention of the emoji.
*/
static std::string get_mention(std::string_view name, snowflake id, bool is_animated = false);
/**
* @brief Emoji requires colons
*
* @return true Requires colons
* @return false Does not require colons
*/
bool requires_colons() const;
/**
* @brief Emoji is managed
*
* @return true Is managed
* @return false Is not managed
*/
bool is_managed() const;
/**
* @brief Emoji is animated
*
* @return true Is animated
* @return false Is noy animated
*/
bool is_animated() const;
/**
* @brief Is available
*
* @return true Is available
* @return false Is unavailable
*/
bool is_available() const;
/**
* @brief Load an image into the object
*
* @param image_blob Image binary data
* @param type Type of image. It can be one of `i_gif`, `i_jpg` or `i_png`.
* @return emoji& Reference to self
* @throw dpp::length_exception Image content exceeds discord maximum of 256 kilobytes
*/
emoji& load_image(std::string_view image_blob, const image_type type);
/**
* @brief Load an image into the object
*
* @param data Image binary data
* @param size Size of the image.
* @param type Type of image. It can be one of `i_gif`, `i_jpg` or `i_png`.
* @return emoji& Reference to self
* @throw dpp::length_exception Image content exceeds discord maximum of 256 kilobytes
*/
emoji& load_image(const std::byte* data, uint32_t size, const image_type type);
/**
* @brief Format to name if unicode, name:id if has id or a:name:id if animated
*
* @return Formatted name for reactions
*/
std::string format() const;
/**
* @brief Get the mention/ping for the emoji
*
* @return std::string mention
*/
std::string get_mention() const;
/**
* @brief Get the custom emoji url
*
* @param size The size of the emoji in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized emoji is returned.
* @param format The format to use for the emoji. It can be one of `i_webp`, `i_jpg`, `i_png` or `i_gif`.
* When passing `i_gif`, it returns an empty string for non-animated emojis. Consider using the `prefer_animated` parameter instead.
* @param prefer_animated Whether you prefer gif format.
* If true, it'll return gif format whenever the emoji is available as animated.
* @return std::string emoji url or an empty string, if the id is not set
*/
std::string get_url(uint16_t size = 0, const image_type format = i_png, bool prefer_animated = true) const;
};
/**
* @brief Group of emojis
*/
typedef std::unordered_map<snowflake, emoji> emoji_map;
}

246
include/dpp/entitlement.h Normal file
View File

@ -0,0 +1,246 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
#include <unordered_map>
namespace dpp {
/**
* @brief The type of entitlement.
* */
enum entitlement_type : uint8_t {
/**
* @brief Entitlement was purchased by user
*/
PURCHASE = 1,
/**
* @brief Entitlement for Discord Nitro subscription
*/
PREMIUM_SUBSCRIPTION = 2,
/**
* @brief Entitlement was gifted by developer
*/
DEVELOPER_GIFT = 3,
/**
* @brief Entitlement was purchased by a dev in application test mode
*/
TEST_MODE_PURCHASE = 4,
/**
* @brief Entitlement was granted when the SKU was free
*/
FREE_PURCHASE = 5,
/**
* @brief Entitlement was gifted by another user
*/
USER_GIFT = 6,
/**
* @brief Entitlement was claimed by user for free as a Nitro Subscriber
*/
PREMIUM_PURCHASE = 7,
/**
* @brief Entitlement was purchased as an app subscription
*/
APPLICATION_SUBSCRIPTION = 8,
};
/**
* @brief Entitlement flags.
*/
enum entitlement_flags : uint8_t {
/**
* @brief Entitlement was deleted
*
* @note Only discord staff can delete an entitlement via
* their internal tooling. It should rarely happen except in cases
* of fraud or chargeback.
*/
ent_deleted = 0b0000001,
/**
* @brief Entitlement was consumed.
*
* @note A consumed entitlement is a used-up one-off purchase.
*/
ent_consumed = 0b0000010,
};
/**
* @brief A definition of a discord entitlement.
*
* An entitlement is a user's connection to an SKU, basically a subscription
* or a one-off purchase.
*/
class DPP_EXPORT entitlement : public managed, public json_interface<entitlement> {
protected:
friend struct json_interface<entitlement>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
entitlement& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build json for this entitlement object
*
* @param with_id include the ID in the json
* @return json JSON object
*/
json to_json_impl(bool with_id = false) const;
public:
/**
* @brief ID of the entitlement event
*
* Not sure if this remains constant, it does not relate to the SKU,
* user, guild or subscription. Do not use it for anything except state
* tracking.
*/
snowflake sku_id{0};
/**
* @brief ID of the parent application
*/
snowflake application_id{0};
/**
* @brief Subscription ID
*
* This is a unique identifier of the user or guilds subscription to the SKU.
* It won't ever change.
*/
snowflake subscription_id{0};
/**
* @brief Promotion id
*
* These are undocumented but given in examples in the docs.
*/
snowflake promotion_id{0};
/**
* @brief Gift Code Flags (undocumented)
*
* Undocumented, but given in examples in the docs.
*/
uint8_t gift_code_flags{0};
/**
* @brief Optional: ID of the user that is granted access to the entitlement's SKU
*/
snowflake user_id{0};
/**
* @brief Optional: ID of the user that is granted access to the entitlement's SKU
*
* If a guild is provided, according to the examples the user who triggered the
* purchase will also be passed in the user ID. The presence of a non-zero guild
* id snowflake is indication it is a guild subscription.
*/
snowflake guild_id{0};
/**
* @brief The type of entitlement.
*/
entitlement_type type = entitlement_type::APPLICATION_SUBSCRIPTION;
/**
* @brief Optional: Start date at which the entitlement is valid.
*
* @note Not present when using test entitlements.
*/
time_t starts_at{0};
/**
* @brief Optional: Date at which the entitlement is no longer valid.
*
* @note Not present when using test entitlements.
*/
time_t ends_at{0};
/**
* @brief Flags bitmap from dpp::entitlement_flags
*/
uint16_t flags{0};
/**
* @brief Construct a new entitlement object
*/
entitlement() = default;
/**
* @brief Construct a new entitlement object with sku_id, ID, application_id, type, and flags.
*
* @param sku_id The ID of the SKU.
* @param id The ID of the entitlement.
* @param application_id The ID of the parent application.
* @param type The type of entitlement (Should only ever be APPLICATION_SUBSCRIPTION unless you going to use this object as a parameter for dpp::cluster::entitlement_test_create).
* @param flags The flags for the SKU from dpp::entitlement_flags.
*/
entitlement(const snowflake sku_id, const snowflake id = 0, const snowflake application_id = 0, const entitlement_type type = dpp::entitlement_type::APPLICATION_SUBSCRIPTION, const uint8_t flags = 0);
/**
* @brief Get the type of entitlement.
*
* @return entitlement_type Entitlement type
*/
[[nodiscard]] entitlement_type get_type() const;
/**
* @brief Was the entitlement consumed?
*
* A consumed entitlement is a one off purchase which
* has been claimed as used by the application. for example
* in-app purchases.
*
* @return true if the entitlement was consumed.
*/
[[nodiscard]] bool is_consumed() const;
/**
* @brief Was the entitlement deleted?
*
* @return true if the entitlement was deleted.
*/
[[nodiscard]] bool is_deleted() const;
};
/**
* @brief Group of entitlements.
*/
typedef std::unordered_map<snowflake, entitlement> entitlement_map;
}

711
include/dpp/etf.h Normal file
View File

@ -0,0 +1,711 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
* Parts of this file inspired by, or outright copied from erlpack:
* https://github.com/discord/erlpack/
*
* Acknowledgements:
*
* sysdep.h:
* Based on work by FURUHASHI Sadayuki in msgpack-python
* (https://github.com/msgpack/msgpack-python)
*
* Copyright (C) 2008-2010 FURUHASHI Sadayuki
* Licensed under the Apache License, Version 2.0 (the "License").
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
namespace dpp {
/**
* @brief Current ETF format version in use
*/
const uint8_t FORMAT_VERSION = 131;
/**
* @brief Represents a token which identifies the type of value which follows it
* in the ETF binary structure.
*/
enum etf_token_type : uint8_t {
/**
* @brief 68 [Distribution header]
*/
ett_distribution = 'D',
/**
* @brief 70 [Float64:IEEE float]
*/
ett_new_float = 'F',
/**
* @brief 77 [UInt32:Len, UInt8:Bits, Len:Data]
*/
ett_bit_binary = 'M',
/**
* @brief 80 [UInt4:UncompressedSize, N:ZlibCompressedData]
*/
ett_compressed = 'P',
/**
* @brief 97 [UInt8:Int]
*/
ett_smallint = 'a',
/**
* @brief 98 [Int32:Int]
*/
ett_integer = 'b',
/**
* @brief 99 [31:Float String] Float in string format (formatted "%.20e", sscanf "%lf").
*
* @note Superseded by ett_new_float.
*/
ett_float = 'c',
/**
* @brief 100 [UInt16:Len, Len:AtomName] max Len is 255
*/
ett_atom = 'd',
/**
* @brief 101 [atom:Node, UInt32:ID, UInt8:Creation]
*/
ett_reference = 'e',
/**
* @brief 102 [atom:Node, UInt32:ID, UInt8:Creation]
*/
ett_port = 'f',
/**
* @brief 103 [atom:Node, UInt32:ID, UInt32:Serial, UInt8:Creation]
*/
ett_pid = 'g',
/**
* @brief 104 [UInt8:Arity, N:Elements]
*/
ett_small_tuple = 'h',
/**
* @brief 105 [UInt32:Arity, N:Elements]
*/
ett_large_tuple = 'i',
/**
* @brief 106 empty list
*/
ett_nil = 'j',
/**
* @brief 107 [UInt16:Len, Len:Characters]
*/
ett_string = 'k',
/**
* @brief 108 [UInt32:Len, Elements, Tail]
*/
ett_list = 'l',
/**
* @brief 109 [UInt32:Len, Len:Data]
*/
ett_binary = 'm',
/**
* @brief 110 [UInt8:n, UInt8:Sign, n:nums]
*/
ett_bigint_small = 'n',
/**
* @brief 111 [UInt32:n, UInt8:Sign, n:nums]
*/
ett_bigint_large = 'o',
/**
* @brief 112 [UInt32:Size, UInt8:Arity, 16*Uint6-MD5:Uniq, UInt32:Index, UInt32:NumFree, atom:Module, int:OldIndex, int:OldUniq, pid:Pid, NunFree*ext:FreeVars]
*/
ett_new_function = 'p',
/**
* @brief 113 [atom:Module, atom:Function, smallint:Arity]
*/
ett_export = 'q',
/**
* @brief 114 [UInt16:Len, atom:Node, UInt8:Creation, Len*UInt32:ID]
*/
ett_new_reference = 'r',
/**
* @brief 115 [UInt8:Len, Len:AtomName]
*/
ett_atom_small = 's',
/**
* @brief 116 [UInt32:Airty, N:Pairs]
*/
ett_map = 't',
/**
* @brief 117 [UInt4:NumFree, pid:Pid, atom:Module, int:Index, int:Uniq, NumFree*ext:FreeVars]
*/
ett_function = 'u',
/**
* @brief 118 [UInt16:Len, Len:AtomName] max Len is 255 characters (up to 4 bytes per)
*/
ett_atom_utf8 = 'v',
/**
* @brief 119 [UInt8:Len, Len:AtomName]
*/
ett_atom_utf8_small = 'w'
};
/**
* @brief Represents a buffer of bytes being encoded into ETF
*/
struct DPP_EXPORT etf_buffer {
/**
* @brief Raw buffer
*/
std::vector<char> buf;
/**
* @brief Current used length of buffer
* (this is different from buf.size() as it is pre-allocated
* using resize and may not all be in use)
*/
size_t length;
/**
* @brief Construct a new etf buffer object
*
* @param initial initial buffer size to allocate
*/
etf_buffer(size_t initial);
/**
* @brief Destroy the etf buffer object
*/
~etf_buffer();
};
/**
* @brief The etf_parser class can serialise and deserialise ETF (Erlang Term Format)
* into and out of an nlohmann::json object, so that layers above the websocket don't
* have to be any different for handling ETF.
*/
class DPP_EXPORT etf_parser {
/**
* @brief Current size of binary data
*/
size_t size;
/**
* @brief Current offset into binary data
*/
size_t offset;
/**
* @brief Pointer to binary ETF data to be decoded
*/
uint8_t* data;
/**
* @brief Parse a single value, and if that value contains other
* values (e.g. an array or map) then call itself recursively.
*
* @return nlohmann::json JSON value from the ETF
*/
nlohmann::json inner_parse();
/**
* @brief Read 8 bits of data from the buffer
*
* @return uint8_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint8_t read_8_bits();
/**
* @brief Read 16 bits of data from the buffer
*
* @return uint16_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint16_t read_16_bits();
/**
* @brief Read 32 bits of data from the buffer
*
* @return uint32_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint32_t read_32_bits();
/**
* @brief Read 64 bits of data from the buffer
*
* @return uint64_t data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
uint64_t read_64_bits();
/**
* @brief Read string data from the buffer
*
* @param length Length of string to retrieve
* @return const char* data retrieved
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
const char* read_string(uint32_t length);
/**
* @brief Process an 'atom' value.
* An atom is a "label" or constant value within the data,
* such as a key name, nullptr, or false.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json process_atom(const char* atom, uint16_t length);
/**
* @brief Decode an 'atom' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_atom();
/**
* @brief Decode a small 'atom' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_small_atom();
/**
* @brief Decode a small integer value (0-255).
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_small_integer();
/**
* @brief Decode an integer value (-MAXINT -> MAXINT-1).
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_integer();
/**
* @brief Decode an array of values.
*
* @return nlohmann::json values converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_array(uint32_t length);
/**
* @brief Decode a list of values.
*
* @return nlohmann::json values converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_list();
/**
* @brief Decode a 'tuple' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_tuple(uint32_t length);
/**
* @brief Decode a nil 'atom' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_nil();
/**
* @brief Decode a map (object) value.
* Will recurse to evaluate each member variable.
*
* @return nlohmann::json values converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_map();
/**
* @brief Decode a floating point numeric value.
* (depreciated in erlang but still expected to be supported)
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_float();
/**
* @brief Decode a floating type numeric value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_new_float();
/**
* @brief Decode a 'bigint' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_bigint(uint32_t digits);
/**
* @brief Decode a small 'bigint' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_bigint_small();
/**
* @brief Decode a large 'bigint' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_bigint_large();
/**
* @brief Decode a binary value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_binary();
/**
* @brief Decode a string value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_string();
/**
* @brief Decode a string list value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_string_as_list();
/**
* @brief Decode a 'small tuple' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_tuple_small();
/**
* @brief Decode a 'large tuple' value.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_tuple_large();
/**
* @brief Decode a compressed value.
* This is a zlib-compressed binary blob which contains another
* ETF object.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_compressed();
/**
* @brief Decode a 'reference' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_reference();
/**
* @brief Decode a 'new reference' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_new_reference();
/**
* @brief Decode a 'port' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_port();
/**
* @brief Decode a 'PID' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_pid();
/**
* @brief Decode an 'export' value.
* Erlang expects this to be supported, in practice Discord doesn't send these right now.
*
* @return nlohmann::json value converted to JSON
* @throw dpp::exception Data stream isn't long enough to fetch requested bits
*/
nlohmann::json decode_export();
/**
* @brief Write to output buffer for creation of ETF from JSON
*
* @param pk buffer struct
* @param bytes byte buffer to write
* @param l number of bytes to write
* @throw std::exception Buffer cannot be extended
*/
void buffer_write(etf_buffer *pk, const char *bytes, size_t l);
/**
* @brief Append version number to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_version(etf_buffer *b);
/**
* @brief Append nil value to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_nil(etf_buffer *b);
/**
* @brief Append false value to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_false(etf_buffer *b);
/**
* @brief Append true value to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_true(etf_buffer *b);
/**
* @brief Append small integer value to ETF buffer
*
* @param b buffer to append to
* @param d double to append
* @throw std::exception Buffer cannot be extended
*/
void append_small_integer(etf_buffer *b, unsigned char d);
/**
* @brief Append integer value to ETF buffer
*
* @param b buffer to append to
* @param d integer to append
* @throw std::exception Buffer cannot be extended
*/
void append_integer(etf_buffer *b, int32_t d);
/**
* @brief Append 64 bit integer value to ETF buffer
*
* @param b buffer to append to
* @param d integer to append
* @throw std::exception Buffer cannot be extended
*/
void append_unsigned_long_long(etf_buffer *b, unsigned long long d);
/**
* @brief Append 64 bit integer value to ETF buffer
*
* @param b buffer to append to
* @param d integer to append
* @throw std::exception Buffer cannot be extended
*/
void append_long_long(etf_buffer *b, long long d);
/**
* @brief Append double value to ETF buffer
*
* @param b buffer to append to
* @param f double to append
* @throw std::exception Buffer cannot be extended
*/
void append_double(etf_buffer *b, double f);
/**
* @brief Append atom value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_atom(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append utf8 atom value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_atom_utf8(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append binary value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_binary(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append string value to ETF buffer
*
* @param b buffer to append to
* @param bytes pointer to string to append
* @param size size of string to append
* @throw std::exception Buffer cannot be extended
*/
void append_string(etf_buffer *b, const char *bytes, size_t size);
/**
* @brief Append tuple value to ETF buffer
*
* @param b buffer to append to
* @param size size of value to append
* @throw std::exception Buffer cannot be extended
*/
void append_tuple_header(etf_buffer *b, size_t size);
/**
* @brief Append list terminator to ETF buffer
*
* @param b buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void append_nil_ext(etf_buffer *b);
/**
* @brief Append a list header value to ETF buffer
*
* @param b buffer to append to
* @param size size of values to append
* @throw std::exception Buffer cannot be extended
*/
void append_list_header(etf_buffer *b, size_t size);
/**
* @brief Append a map header value to ETF buffer
*
* @param b buffer to append to
* @param size size of values to append
* @throw std::exception Buffer cannot be extended
*/
void append_map_header(etf_buffer *b, size_t size);
/**
* @brief Build ETF buffer
*
* @param j JSON object to build from
* @param b Buffer to append to
* @throw std::exception Buffer cannot be extended
*/
void inner_build(const nlohmann::json* j, etf_buffer* b);
public:
/**
* @brief Construct a new etf parser object
*/
etf_parser();
/**
* @brief Destroy the etf parser object
*/
~etf_parser();
/**
* @brief Convert ETF binary content to nlohmann::json
*
* @param in Raw binary ETF data (generally from a websocket)
* @return nlohmann::json JSON data for use in the library
* @throw dpp::exception Malformed or otherwise invalid ETF content
*/
nlohmann::json parse(const std::string& in);
/**
* @brief Create ETF binary data from nlohmann::json
*
* @param j JSON value to encode to ETF
* @return std::string raw ETF data. Note that this can
* and probably will contain null values, use std::string::data()
* and std::string::size() to manipulate or send it.
* @throw std::exception Not enough memory, or invalid data types/values
*/
std::string build(const nlohmann::json& j);
};
}

159
include/dpp/event.h Normal file
View File

@ -0,0 +1,159 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
#define event_decl(x,wstype) /** @brief Internal event handler for wstype websocket events. Called for each websocket message of this type. @internal */ \
class x : public event { public: virtual void handle(class dpp::discord_client* client, nlohmann::json &j, const std::string &raw); };
/**
* @brief The events namespace holds the internal event handlers for each websocket event.
* These are handled internally and also dispatched to the user code if the event is hooked.
*/
namespace dpp::events {
/**
* @brief An event object represents an event handled internally, passed from the websocket e.g. MESSAGE_CREATE.
*/
class DPP_EXPORT event {
public:
/**
* @brief Pure virtual method for event handler code
* @param client The creating shard
* @param j The json data of the event
* @param raw The raw event json
*/
virtual void handle(class discord_client* client, nlohmann::json &j, const std::string &raw) = 0;
};
/* Internal logger */
event_decl(logger,LOG);
/* Guilds */
event_decl(guild_create,GUILD_CREATE);
event_decl(guild_update,GUILD_UPDATE);
event_decl(guild_delete,GUILD_DELETE);
event_decl(guild_ban_add,GUILD_BAN_ADD);
event_decl(guild_ban_remove,GUILD_BAN_REMOVE);
event_decl(guild_emojis_update,GUILD_EMOJIS_UPDATE);
event_decl(guild_integrations_update,GUILD_INTEGRATIONS_UPDATE);
event_decl(guild_join_request_delete,GUILD_JOIN_REQUEST_DELETE);
event_decl(guild_stickers_update,GUILD_STICKERS_UPDATE);
/* Stage channels */
event_decl(stage_instance_create,STAGE_INSTANCE_CREATE);
event_decl(stage_instance_update,STAGE_INSTANCE_UPDATE);
event_decl(stage_instance_delete,STAGE_INSTANCE_DELETE);
/* Guild members */
event_decl(guild_member_add,GUILD_MEMBER_ADD);
event_decl(guild_member_remove,GUILD_MEMBER_REMOVE);
event_decl(guild_members_chunk,GUILD_MEMBERS_CHUNK);
event_decl(guild_member_update,GUILD_MEMBERS_UPDATE);
/* Guild roles */
event_decl(guild_role_create,GUILD_ROLE_CREATE);
event_decl(guild_role_update,GUILD_ROLE_UPDATE);
event_decl(guild_role_delete,GUILD_ROLE_DELETE);
/* Session state */
event_decl(resumed,RESUMED);
event_decl(ready,READY);
/* Channels */
event_decl(channel_create,CHANNEL_CREATE);
event_decl(channel_update,CHANNEL_UPDATE);
event_decl(channel_delete,CHANNEL_DELETE);
event_decl(channel_pins_update,CHANNEL_PINS_UPDATE);
/* Threads */
event_decl(thread_create,THREAD_CREATE);
event_decl(thread_update,THREAD_UPDATE);
event_decl(thread_delete,THREAD_DELETE);
event_decl(thread_list_sync,THREAD_LIST_SYNC);
event_decl(thread_member_update,THREAD_MEMBER_UPDATE);
event_decl(thread_members_update,THREAD_MEMBERS_UPDATE);
/* Messages */
event_decl(message_create,MESSAGE_CREATE);
event_decl(message_update,MESSAGE_UPDATE);
event_decl(message_delete,MESSAGE_DELETE);
event_decl(message_delete_bulk,MESSAGE_DELETE_BULK);
event_decl(message_poll_vote_add,MESSAGE_POLL_VOTE_ADD);
event_decl(message_poll_vote_remove,MESSAGE_POLL_VOTE_REMOVE);
/* Presence/typing */
event_decl(presence_update,PRESENCE_UPDATE);
event_decl(typing_start,TYPING_START);
/* Users (outside of guild) */
event_decl(user_update,USER_UPDATE);
/* Message reactions */
event_decl(message_reaction_add,MESSAGE_REACTION_ADD);
event_decl(message_reaction_remove,MESSAGE_REACTION_REMOVE);
event_decl(message_reaction_remove_all,MESSAGE_REACTION_REMOVE_ALL);
event_decl(message_reaction_remove_emoji,MESSAGE_REACTION_REMOVE_EMOJI);
/* Invites */
event_decl(invite_create,INVITE_CREATE);
event_decl(invite_delete,INVITE_DELETE);
/* Voice */
event_decl(voice_state_update,VOICE_STATE_UPDATE);
event_decl(voice_server_update,VOICE_SERVER_UPDATE);
/* Webhooks */
event_decl(webhooks_update,WEBHOOKS_UPDATE);
/* Application commands */
event_decl(interaction_create,INTERACTION_CREATE);
/* Integrations */
event_decl(integration_create,INTEGRATION_CREATE);
event_decl(integration_update,INTEGRATION_UPDATE);
event_decl(integration_delete,INTEGRATION_DELETE);
/* Scheduled events */
event_decl(guild_scheduled_event_create,GUILD_SCHEDULED_EVENT_CREATE);
event_decl(guild_scheduled_event_update,GUILD_SCHEDULED_EVENT_UPDATE);
event_decl(guild_scheduled_event_delete,GUILD_SCHEDULED_EVENT_DELETE);
event_decl(guild_scheduled_event_user_add,GUILD_SCHEDULED_EVENT_USER_ADD);
event_decl(guild_scheduled_event_user_remove,GUILD_SCHEDULED_EVENT_USER_REMOVE);
/* Auto moderation */
event_decl(automod_rule_create, AUTO_MODERATION_RULE_CREATE);
event_decl(automod_rule_update, AUTO_MODERATION_RULE_UPDATE);
event_decl(automod_rule_delete, AUTO_MODERATION_RULE_DELETE);
event_decl(automod_rule_execute, AUTO_MODERATION_ACTION_EXECUTION);
/* Audit log */
event_decl(guild_audit_log_entry_create, GUILD_AUDIT_LOG_ENTRY_CREATE);
/* Entitlements */
event_decl(entitlement_create, ENTITLEMENT_CREATE);
event_decl(entitlement_update, ENTITLEMENT_UPDATE);
event_decl(entitlement_delete, ENTITLEMENT_DELETE);
}

744
include/dpp/event_router.h Normal file
View File

@ -0,0 +1,744 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <map>
#include <variant>
#include <dpp/snowflake.h>
#include <dpp/misc-enum.h>
#include <dpp/json_fwd.h>
#include <algorithm>
#include <mutex>
#include <shared_mutex>
#include <cstring>
#include <atomic>
#include <dpp/exception.h>
#include <dpp/coro/job.h>
#include <dpp/coro/task.h>
namespace dpp {
#ifndef DPP_NO_CORO
template <typename T>
class event_router_t;
namespace detail {
/** @brief Internal cogwheels for dpp::event_router_t */
namespace event_router {
/** @brief State of an owner of an event_router::awaitable */
enum class awaiter_state {
/** @brief Awaitable is not being awaited */
none,
/** @brief Awaitable is being awaited */
waiting,
/** @brief Awaitable will be resumed imminently */
resuming,
/** @brief Awaitable will be cancelled imminently */
cancelling
};
/**
* @brief Awaitable object representing an event.
* A user can co_await on this object to resume the next time the event is fired,
* optionally with a condition.
*/
template <typename T>
class awaitable {
friend class event_router_t<T>;
/** @brief Resume the coroutine waiting on this object */
void resume() {
std_coroutine::coroutine_handle<>::from_address(handle).resume();
}
/** @brief Event router that will manage this object */
event_router_t<T> *self;
/** @brief Predicate on the event, or nullptr for always match */
std::function<bool (const T &)> predicate = nullptr;
/** @brief Event that triggered a resumption, to give to the resumer */
const T *event = nullptr;
/** @brief Coroutine handle, type-erased */
void* handle = nullptr;
/** @brief The state of the awaiting coroutine */
std::atomic<awaiter_state> state = awaiter_state::none;
/** Default constructor is accessible only to event_router_t */
awaitable() = default;
/** Normal constructor is accessible only to event_router_t */
template <typename F>
awaitable(event_router_t<T> *router, F&& fun) : self{router}, predicate{std::forward<F>(fun)} {}
public:
/** This object is not copyable. */
awaitable(const awaitable &) = delete;
/** Move constructor. */
awaitable(awaitable &&rhs) noexcept : self{rhs.self}, predicate{std::move(rhs.predicate)}, event{rhs.event}, handle{std::exchange(rhs.handle, nullptr)}, state{rhs.state.load(std::memory_order_relaxed)} {}
/** This object is not copyable. */
awaitable& operator=(const awaitable &) = delete;
/** Move assignment operator. */
awaitable& operator=(awaitable&& rhs) noexcept {
self = rhs.self;
predicate = std::move(rhs.predicate);
event = rhs.event;
handle = std::exchange(rhs.handle, nullptr);
state = rhs.state.load(std::memory_order_relaxed);
return *this;
}
/**
* @brief Request cancellation. This will detach this object from the event router and resume the awaiter, which will be thrown dpp::task_cancelled::exception.
*
* @throw ??? As this resumes the coroutine, it may throw any exceptions at the caller.
*/
void cancel();
/**
* @brief First function called by the standard library when awaiting this object. Returns true if we need to suspend.
*
* @retval false always.
*/
[[nodiscard]] constexpr bool await_ready() const noexcept;
/**
* @brief Second function called by the standard library when awaiting this object, after suspension.
* This will attach the object to its event router, to be resumed on the next event that satisfies the predicate.
*
* @return void never resume on call.
*/
void await_suspend(detail::std_coroutine::coroutine_handle<> caller);
/**
* @brief Third and final function called by the standard library, called when resuming the coroutine.
*
* @throw @ref task_cancelled_exception if cancel() has been called
* @return const T& __Reference__ to the event that matched
*/
[[maybe_unused]] const T& await_resume();
};
}
}
#endif
/**
* @brief A returned event handle for an event which was attached
*/
typedef size_t event_handle;
/**
* @brief Handles routing of an event to multiple listeners.
* Multiple listeners may attach to the event_router_t by means of @ref operator()(F&&) "operator()". Passing a
* lambda into @ref operator()(F&&) "operator()" attaches to the event.
*
* @details Dispatchers of the event may call the @ref call() method to cause all listeners
* to receive the event.
*
* The @ref empty() method will return true if there are no listeners attached
* to the event_router_t (this can be used to save time by not constructing objects that
* nobody will ever see).
*
* The @ref detach() method removes an existing listener from the event,
* using the event_handle ID returned by @ref operator()(F&&) "operator()".
*
* This class is used by the library to route all websocket events to listening code.
*
* Example:
*
* @code{cpp}
* // Declare an event that takes log_t as its parameter
* event_router_t<log_t> my_event;
*
* // Attach a listener to the event
* event_handle id = my_event([&](const log_t& cc) {
* std::cout << cc.message << "\n";
* });
*
* // Construct a log_t and call the event (listeners will receive the log_t object)
* log_t lt;
* lt.message = "foo";
* my_event.call(lt);
*
* // Detach from an event using the handle returned by operator()
* my_event.detach(id);
* @endcode
*
* @tparam T type of single parameter passed to event lambda derived from event_dispatch_t
*/
template<class T> class event_router_t {
private:
friend class cluster;
/**
* @brief Non-coro event handler type
*/
using regular_handler_t = std::function<void(const T&)>;
/**
* @brief Type that event handlers will be stored as with DPP_CORO off.
* This is the ABI DPP_CORO has to match.
*/
using event_handler_abi_t = std::variant<regular_handler_t, std::function<task_dummy(T)>>;
#ifndef DPP_NO_CORO
friend class detail::event_router::awaitable<T>;
/** @brief dpp::task coro event handler */
using task_handler_t = std::function<dpp::task<void>(const T&)>;
/** @brief Type that event handlers are stored as */
using event_handler_t = std::variant<regular_handler_t, task_handler_t>;
DPP_CHECK_ABI_COMPAT(event_handler_t, event_handler_abi_t)
#else
/**
* @brief Type that event handlers are stored as
*/
using event_handler_t = event_handler_abi_t;
#endif
/**
* @brief Identifier for the next event handler, will be given to the user on attaching a handler
*/
event_handle next_handle = 1;
/**
* @brief Thread safety mutex
*/
mutable std::shared_mutex mutex;
/**
* @brief Container of event listeners keyed by handle,
* as handles are handed out sequentially they will always
* be called in they order they are bound to the event
* as std::map is an ordered container.
*/
std::map<event_handle, event_handler_t> dispatch_container;
#ifndef DPP_NO_CORO
/**
* @brief Mutex for messing with coro_awaiters.
*/
mutable std::shared_mutex coro_mutex;
/**
* @brief Vector containing the awaitables currently being awaited on for this event router.
*/
mutable std::vector<detail::event_router::awaitable<T> *> coro_awaiters;
#else
/**
* @brief Dummy for ABI compatibility between DPP_CORO and not
*/
utility::dummy<std::shared_mutex> definitely_not_a_mutex;
/**
* @brief Dummy for ABI compatibility between DPP_CORO and not
*/
utility::dummy<std::vector<void*>> definitely_not_a_vector;
#endif
/**
* @brief A function to be called whenever the method is called, to check
* some condition that is required for this event to trigger correctly.
*/
std::function<void(const T&)> warning;
/**
* @brief Next handle to be given out by the event router
*/
protected:
/**
* @brief Set the warning callback object used to check that this
* event is capable of running properly
*
* @param warning_function A checking function to call
*/
void set_warning_callback(std::function<void(const T&)> warning_function) {
warning = warning_function;
}
/**
* @brief Handle an event. This function should only be used without coro enabled, otherwise use handle_coro.
*/
void handle(const T& event) const {
if (warning) {
warning(event);
}
std::shared_lock l(mutex);
for (const auto& [_, listener] : dispatch_container) {
if (!event.is_cancelled()) {
if (std::holds_alternative<regular_handler_t>(listener)) {
std::get<regular_handler_t>(listener)(event);
} else {
throw dpp::logic_exception("cannot handle a coroutine event handler with a library built without DPP_CORO");
}
}
};
}
#ifndef DPP_NO_CORO
/**
* @brief Handle an event as a coroutine, ensuring the lifetime of the event object.
*/
dpp::job handle_coro(T event) const {
if (warning) {
warning(event);
}
resume_awaiters(event);
std::vector<dpp::task<void>> tasks;
{
std::shared_lock l(mutex);
for (const auto& [_, listener] : dispatch_container) {
if (!event.is_cancelled()) {
if (std::holds_alternative<task_handler_t>(listener)) {
tasks.push_back(std::get<task_handler_t>(listener)(event));
} else if (std::holds_alternative<regular_handler_t>(listener)) {
std::get<regular_handler_t>(listener)(event);
}
}
};
}
for (dpp::task<void>& t : tasks) {
co_await t; // keep the event object alive until all tasks finished
}
}
/**
* @brief Attach a suspended coroutine to this event router via detail::event_router::awaitable.
* It will be resumed and detached when an event satisfying its condition completes, or it is cancelled.
*
* This is for internal usage only, the user way to do this is to co_await it (which will call this when suspending)
* This guarantees that the coroutine is indeed suspended and thus can be resumed at any time
*
* @param awaiter Awaiter to attach
*/
void attach_awaiter(detail::event_router::awaitable<T> *awaiter) {
std::unique_lock lock{coro_mutex};
coro_awaiters.emplace_back(awaiter);
}
/**
* @brief Detach an awaiting coroutine handle from this event router.
* This is mostly called when a detail::event_router::awaitable is cancelled.
*
* @param handle Coroutine handle to find in the attached coroutines
*/
void detach_coro(void *handle) {
std::unique_lock lock{coro_mutex};
coro_awaiters.erase(std::remove_if(coro_awaiters.begin(), coro_awaiters.end(), [handle](detail::event_router::awaitable<T> const *awaiter) { return awaiter->handle == handle; }), coro_awaiters.end());
}
/**
* @brief Resume any awaiter whose predicate matches this event, or is null.
*
* @param event Event to compare and pass to accepting awaiters
*/
void resume_awaiters(const T& event) const {
std::vector<detail::event_router::awaitable<T>*> to_resume;
std::unique_lock lock{coro_mutex};
for (auto it = coro_awaiters.begin(); it != coro_awaiters.end();) {
detail::event_router::awaitable<T>* awaiter = *it;
if (awaiter->predicate && !awaiter->predicate(event)) {
++it;
} else {
using state_t = detail::event_router::awaiter_state;
/**
* If state == none (was never awaited), do nothing
* If state == waiting, prevent resumption, resume on our end
* If state == resuming || cancelling, ignore
*
* Technically only cancelling || waiting should be possible here
* We do this by trying to exchange "waiting" with "resuming". If that returns false, this is presumed to be "cancelling"
*/
state_t s = state_t::waiting;
if (awaiter->state.compare_exchange_strong(s, state_t::resuming)) {
to_resume.emplace_back(awaiter);
awaiter->event = &event;
it = coro_awaiters.erase(it);
} else {
++it;
}
}
}
lock.unlock();
for (detail::event_router::awaitable<T>* awaiter : to_resume)
awaiter->resume();
}
#endif
public:
/**
* @brief Construct a new event_router_t object.
*/
event_router_t() = default;
/**
* @brief Destructor. Will cancel any coroutine awaiting on events.
*
* @throw ! Cancelling a coroutine will throw a dpp::task_cancelled_exception to it.
* This will be caught in this destructor, however, make sure no other exceptions are thrown in the coroutine after that or it will terminate.
*/
~event_router_t() {
#ifndef DPP_NO_CORO
while (!coro_awaiters.empty()) {
// cancel all awaiters. here we cannot do the usual loop as we'd need to lock coro_mutex, and cancel() locks and modifies coro_awaiters
try {
coro_awaiters.back()->cancel();
/*
* will resume coroutines and may throw ANY exception, including dpp::task_cancelled_exception cancel() throws at them.
* we catch that one. for the rest, good luck :)
* realistically the only way any other exception would pop up here is if someone catches dpp::task_cancelled_exception THEN throws another exception.
*/
} catch (const dpp::task_cancelled_exception &) {
// ok. likely we threw this one
}
}
#endif
}
/**
* @brief Call all attached listeners.
* Listeners may cancel, by calling the event.cancel method.
*
* @param event Class to pass as parameter to all listeners.
*/
void call(const T& event) const {
#ifndef DPP_NO_CORO
handle_coro(event);
#else
handle(event);
#endif
};
/**
* @brief Call all attached listeners.
* Listeners may cancel, by calling the event.cancel method.
*
* @param event Class to pass as parameter to all listeners.
*/
void call(T&& event) const {
#ifndef DPP_NO_CORO
handle_coro(std::move(event));
#else
handle(std::move(event));
#endif
};
#ifndef DPP_NO_CORO
/**
* @brief Obtain an awaitable object that refers to an event with a certain condition.
* It can be co_await-ed to wait for the next event that satisfies this condition.
* On resumption the awaiter will be given __a reference__ to the event,
* saving it in a variable is recommended to avoid variable lifetime issues.
*
* @details Example: @code{cpp}
* dpp::task<> my_handler(const dpp::slashcommand_t& event) {
* co_await event.co_reply(dpp::message().add_component(dpp::component().add_component().set_label("click me!").set_id("test")));
*
* dpp::button_click_t b = co_await c->on_button_click.with([](const dpp::button_click_t &event){ return event.custom_id == "test"; });
*
* // do something on button click
* }
* @endcode
*
* This can be combined with dpp::when_any and other awaitables, for example dpp::cluster::co_sleep to create @ref expiring-buttons "expiring buttons".
*
* @warning On resumption the awaiter will be given <b>a reference</b> to the event.
* This means that variable may become dangling at the next co_await, be careful and save it in a variable
* if you need to.
* @param pred Predicate to check the event against. This should be a callable of the form `bool(const T&)`
* where T is the event type, returning true if the event is to match.
* @return awaitable An awaitable object that can be co_await-ed to await an event matching the condition.
*/
template <typename Predicate>
#ifndef _DOXYGEN_
requires utility::callable_returns<Predicate, bool, const T&>
#endif
auto when(Predicate&& pred)
#ifndef _DOXYGEN_
noexcept(noexcept(std::function<bool(const T&)>{std::declval<Predicate>()}))
#endif
{
return detail::event_router::awaitable<T>{this, std::forward<Predicate>(pred)};
}
/**
* @brief Obtain an awaitable object that refers to any event.
* It can be co_await-ed to wait for the next event.
*
* @details Example: @code{cpp}
* dpp::task<> my_handler(const dpp::slashcommand_t& event) {
* co_await event.co_reply(dpp::message().add_component(dpp::component().add_component().set_label("click me!").set_id("test")));
*
* dpp::button_click_t b = co_await c->on_message_create;
*
* // do something on button click
* }
* @endcode
*
* This can be combined with dpp::when_any and other awaitables, for example dpp::cluster::co_sleep to create expiring buttons.
*
* @warning On resumption the awaiter will be given <b>a reference</b> to the event.
* This means that variable may become dangling at the next co_await, be careful and save it in a variable
* if you need to.
* @return awaitable An awaitable object that can be co_await-ed to await an event matching the condition.
*/
[[nodiscard]] auto operator co_await() noexcept {
return detail::event_router::awaitable<T>{this, nullptr};
}
#endif
/**
* @brief Returns true if the container of listeners is empty,
* i.e. there is nothing listening for this event right now.
*
* @retval true if there are no listeners
* @retval false if there are some listeners
*/
[[nodiscard]] bool empty() const {
#ifndef DPP_NO_CORO
std::shared_lock lock{mutex};
std::shared_lock coro_lock{coro_mutex};
return dispatch_container.empty() && coro_awaiters.empty();
#else
std::shared_lock lock{mutex};
return dispatch_container.empty();
#endif
}
/**
* @brief Returns true if any listeners are attached.
*
* This is the boolean opposite of event_router_t::empty().
* @retval true if listeners are attached
* @retval false if no listeners are attached
*/
operator bool() const {
return !empty();
}
#ifdef _DOXYGEN_
/**
* @brief Attach a callable to the event, adding a listener.
* The callable should either be of the form `void(const T&)` or
* `dpp::task<void>(const T&)`,
* where T is the event type for this event router.
*
* This has the exact same behavior as using \ref attach(F&&) "attach".
*
* @see attach
* @param fun Callable to attach to event
* @return event_handle An event handle unique to this event, used to
* detach the listener from the event later if necessary.
*/
template <typename F>
[[maybe_unused]] event_handle operator()(F&& fun);
/**
* @brief Attach a callable to the event, adding a listener.
* The callable should either be of the form `void(const T&)` or
* `dpp::task<void>(const T&)`,
* where T is the event type for this event router.
*
* @param fun Callable to attach to event
* @return event_handle An event handle unique to this event, used to
* detach the listener from the event later if necessary.
*/
template <typename F>
[[maybe_unused]] event_handle attach(F&& fun);
#else /* not _DOXYGEN_ */
# ifndef DPP_NO_CORO
/**
* @brief Attach a callable to the event, adding a listener.
* The callable should either be of the form `void(const T&)` or
* `dpp::task<void>(const T&)`, where T is the event type for this event router.
*
* @param fun Callable to attach to event
* @return event_handle An event handle unique to this event, used to
* detach the listener from the event later if necessary.
*/
template <typename F>
requires (utility::callable_returns<F, dpp::task<void>, const T&> || utility::callable_returns<F, void, const T&>)
[[maybe_unused]] event_handle operator()(F&& fun) {
return this->attach(std::forward<F>(fun));
}
/**
* @brief Attach a callable to the event, adding a listener.
* The callable should either be of the form `void(const T&)` or
* `dpp::task<void>(const T&)`, where T is the event type for this event router.
*
* @param fun Callable to attach to event
* @return event_handle An event handle unique to this event, used to
* detach the listener from the event later if necessary.
*/
template <typename F>
requires (utility::callable_returns<F, void, const T&>)
[[maybe_unused]] event_handle attach(F&& fun) {
std::unique_lock l(mutex);
event_handle h = next_handle++;
dispatch_container.emplace(std::piecewise_construct, std::forward_as_tuple(h), std::forward_as_tuple(std::in_place_type_t<regular_handler_t>{}, std::forward<F>(fun)));
return h;
}
/**
* @brief Attach a callable to the event, adding a listener.
* The callable should either be of the form `void(const T&)` or
* `dpp::task<void>(const T&)`, where T is the event type for this event router.
*
* @param fun Callable to attach to event
* @return event_handle An event handle unique to this event, used to
* detach the listener from the event later if necessary.
*/
template <typename F>
requires (utility::callable_returns<F, task<void>, const T&>)
[[maybe_unused]] event_handle attach(F&& fun) {
assert(dpp::utility::is_coro_enabled());
std::unique_lock l(mutex);
event_handle h = next_handle++;
dispatch_container.emplace(std::piecewise_construct, std::forward_as_tuple(h), std::forward_as_tuple(std::in_place_type_t<task_handler_t>{}, std::forward<F>(fun)));
return h;
}
# else
/**
* @brief Attach a callable to the event, adding a listener.
* The callable should be of the form `void(const T&)`
* where T is the event type for this event router.
*
* @param fun Callable to attach to event
* @return event_handle An event handle unique to this event, used to
* detach the listener from the event later if necessary.
*/
template <typename F>
[[maybe_unused]] std::enable_if_t<utility::callable_returns_v<F, void, const T&>, event_handle> operator()(F&& fun) {
return this->attach(std::forward<F>(fun));
}
/**
* @brief Attach a callable to the event, adding a listener.
* The callable should be of the form `void(const T&)`
* where T is the event type for this event router.
*f
* @warning You cannot call this within an event handler.
*
* @param fun Callable to attach to event
* @return event_handle An event handle unique to this event, used to
* detach the listener from the event later if necessary.
*/
template <typename F>
[[maybe_unused]] std::enable_if_t<utility::callable_returns_v<F, void, const T&>, event_handle> attach(F&& fun) {
std::unique_lock l(mutex);
event_handle h = next_handle++;
dispatch_container.emplace(h, std::forward<F>(fun));
return h;
}
# endif /* DPP_NO_CORO */
#endif /* _DOXYGEN_ */
/**
* @brief Detach a listener from the event using a previously obtained ID.
*
* @warning You cannot call this within an event handler.
*
* @param handle An ID obtained from @ref operator(F&&) "operator()"
* @retval true The event was successfully detached
* @retval false The ID is invalid (possibly already detached, or does not exist)
*/
[[maybe_unused]] bool detach(const event_handle& handle) {
std::unique_lock l(mutex);
return this->dispatch_container.erase(handle);
}
};
#ifndef DPP_NO_CORO
namespace detail::event_router {
template <typename T>
void awaitable<T>::cancel() {
awaiter_state s = awaiter_state::waiting;
/**
* If state == none (was never awaited), do nothing
* If state == waiting, prevent resumption, resume on our end
* If state == resuming || cancelling, ignore
*/
if (state.compare_exchange_strong(s, awaiter_state::cancelling)) {
self->detach_coro(handle);
resume();
}
}
template <typename T>
constexpr bool awaitable<T>::await_ready() const noexcept {
return false;
}
template <typename T>
void awaitable<T>::await_suspend(detail::std_coroutine::coroutine_handle<> caller) {
state.store(awaiter_state::waiting);
handle = caller.address();
self->attach_awaiter(this);
}
template <typename T>
const T &awaitable<T>::await_resume() {
handle = nullptr;
predicate = nullptr;
if (state.exchange(awaiter_state::none, std::memory_order_relaxed) == awaiter_state::cancelling) {
throw dpp::task_cancelled_exception{"event_router::awaitable was cancelled"};
}
return *std::exchange(event, nullptr);
}
}
#endif
}

607
include/dpp/exception.h Normal file
View File

@ -0,0 +1,607 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <exception>
#include <algorithm>
namespace dpp {
/**
* @brief Exception error codes possible for dpp::exception::code()
*
* This list is a combined list of Discord's error codes, HTTP error codes,
* zlib, opus and C library codes (e.g. DNS, socket etc). You may
* use these to easily identify a type of exception without having to resort
* to string comparison against dpp::exception::what()
*
* For detailed descriptions of each error code, see the text description
* returned in `what()`.
*
* @note Some exceptions MAY have error codes which are NOT in this list
* in the event a C library is updated and adds new codes we did not document
* here. In this case, or where the code is not specific, refer to `what()`.
*/
enum exception_error_code {
err_no_code_specified = 0,
err_zlib_see_errno = -1,
err_zlib_init_stream = -2,
err_zlib_init_data = -3,
err_zlib_init_mem = -4,
err_zlib_init_buffer = -5,
err_zlib_init_version = -6,
err_opus_bad_arg = -11,
err_opus_buffer_too_small = -12,
err_opus_internal_error = -13,
err_opus_invalid_packet = -14,
err_opus_unimplemented = -15,
err_opus_invalid_state = -16,
err_opus_alloc_fail = -17,
err_dns_bad_flags = -21,
err_name_or_service_unknown = -22,
err_dns_again = -23,
err_dns_fail = -24,
err_dns_family = -26,
err_dns_socket_type = -27,
err_dns_service = -28,
err_dns_memory = -30,
err_dns_system_error = -31,
err_dns_overflow = -32,
err_ssl_new = 1,
err_ssl_connect = 2,
err_write = 3,
err_ssl_write = 4,
err_no_sessions_left = 5,
err_auto_shard = 6,
err_reconnection = 7,
err_bind_failure = 8,
err_nonblocking_failure = 9,
err_voice_terminating = 10,
err_connect_failure = 11,
err_ssl_context = 12,
err_ssl_version = 13,
err_invalid_socket = 14,
err_socket_error = 15,
err_websocket_proto_already_set = 16,
err_command_handler_not_ready = 17,
err_no_owning_message = 18,
err_cancelled_event = 19,
err_event_status = 20,
err_event_start_time = 21,
err_event_end_time = 22,
err_command_has_caps = 23,
err_choice_autocomplete = 24,
err_interaction = 25,
err_too_many_component_rows = 26,
err_invalid_webhook = 27,
err_voice_state_timestamp = 28,
err_no_voice_support = 29,
err_invalid_voice_packet_length = 30,
err_opus = 31,
err_cant_start_shard = 32,
err_etf = 33,
err_cache = 34,
err_icon_size = 35,
err_massive_audio = 36,
err_unknown = 37,
err_bad_request = 400,
err_unauthorized = 401,
err_payment_required = 402,
err_forbidden = 403,
err_not_found = 404,
err_method_not_allowed = 405,
err_not_acceptable = 406,
err_proxy_auth_required = 407,
err_request_timeout = 408,
err_conflict = 409,
err_gone = 410,
err_length_required = 411,
err_precondition_failed = 412,
err_payload_too_large = 413,
err_uri_too_long = 414,
err_unsupported_media_type = 415,
err_range_not_satisfiable = 416,
err_expectation_failed = 417,
err_im_a_teapot = 418,
err_page_expired = 419,
err_twitter_rate_limited = 420,
err_misdirected_request = 421,
err_unprocessable_content = 422,
err_webdav_locked = 423,
err_webdav_failed_dependency = 424,
err_too_early = 425,
err_upgrade_required = 426,
err_precondition_required = 428,
err_rate_limited = 429,
err_request_headers_too_large = 431,
err_page_blocked = 450,
err_unavailable_for_legal_reasons = 451,
err_http_request_on_https_port = 497,
err_internal_server_error = 500,
err_not_implemented = 501,
err_bad_gateway = 502,
err_service_unavailable = 503,
err_gateway_timeout = 504,
err_http_version_not_supported = 505,
err_variant_also_negotiates = 506,
err_webdav_insufficient_storage = 507,
err_webdav_loop_detected = 508,
err_bandwidth_limit_exceeded = 509,
err_not_extended = 510,
err_network_auth_required = 511,
err_web_server_down = 521,
err_connection_timed_out = 522,
err_origin_unreachable = 523,
err_timeout = 524,
err_ssl_handshake_failed = 525,
err_invalid_ssl_certificate = 526,
err_railgun = 527,
err_cloudflare = 530,
err_websocket_unknown = 4000,
err_websocket_bad_opcode= 4001,
err_websocket_decode = 4002,
err_websocket_not_authenticated = 4003,
err_websocket_authentication_failed = 4004,
err_websocket_already_authenticated = 4005,
err_websocket_invalid_seq_number = 4007,
err_websocket_rate_limited = 4008,
err_websocket_session_timeout = 4009,
err_websocket_invalid_shard = 4010,
err_websocket_sharding_required = 4011,
err_websocket_invalid_api_version = 4012,
err_websocket_invalid_intents = 4013,
err_websocket_disallowed_intents = 4014,
err_websocket_voice_disconnected = 4014,
err_websocket_voice_server_crashed = 4015,
err_websocket_voice_unknown_encryption = 4016,
err_compression_stream = 6000,
err_compression_data = 6001,
err_compression_memory = 6002,
err_unknown_account = 10001,
err_unknown_application = 10002,
err_unknown_channel = 10003,
err_unknown_guild = 10004,
err_unknown_integration = 10005,
err_unknown_invite = 10006,
err_unknown_member = 10007,
err_unknown_message = 10008,
err_unknown_permission_overwrite = 10009,
err_unknown_provider = 10010,
err_unknown_role = 10011,
err_unknown_token = 10012,
err_unknown_user = 10013,
err_unknown_emoji = 10014,
err_unknown_webhook = 10015,
err_unknown_webhook_service = 10016,
err_unknown_session = 10020,
err_unknown_ban = 10026,
err_unknown_sku = 10027,
err_unknown_store_listing = 10028,
err_unknown_entitlement = 10029,
err_unknown_build = 10030,
err_unknown_lobby = 10031,
err_unknown_branch = 10032,
err_unknown_store_directory_layout = 10033,
err_unknown_redistributable = 10036,
err_unknown_gift_code = 10038,
err_unknown_stream = 10049,
err_unknown_premium_server_subscribe_cooldown = 10050,
err_unknown_guild_template = 10057,
err_unknown_discoverable_server_category = 10059,
err_unknown_sticker = 10060,
err_unknown_interaction = 10062,
err_unknown_application_command = 10063,
err_unknown_voice_state = 10065,
err_unknown_application_command_permissions = 10066,
err_unknown_stage_instance = 10067,
err_unknown_guild_member_verification_form = 10068,
err_unknown_guild_welcome_screen = 10069,
err_unknown_guild_scheduled_event = 10070,
err_unknown_guild_scheduled_event_user = 10071,
err_unknown_tag = 10087,
err_bots_cannot_use_this_endpoint = 20001,
err_only_bots_can_use_this_endpoint = 20002,
err_explicit_content = 20009,
err_unauthorized_for_application = 20012,
err_slowmode_rate_limit = 20016,
err_owner_only = 20018,
err_announcement_rate_limit = 20022,
err_under_minimum_age = 20024,
err_write_rate_limit = 20029,
err_stage_banned_words = 20031,
err_guild_premium_subscription_level_too_low = 20035,
err_guilds = 30001,
err_friends = 30002,
err_pins_for_the_channel = 30003,
err_recipients = 30004,
err_guild_roles = 30005,
err_webhooks = 30007,
err_emojis = 30008,
err_reactions = 30010,
err_group_dms = 30011,
err_guild_channels = 30013,
err_attachments_in_a_message = 30015,
err_invites = 30016,
err_animated_emojis = 30018,
err_server_members = 30019,
err_server_categories = 30030,
err_guild_already_has_a_template = 30031,
err_application_commands = 30032,
err_thread_participants = 30033,
err_daily_application_command_creates = 30034,
err_bans_for_non_guild_members_have_been_exceeded = 30035,
err_bans_fetches = 30037,
err_uncompleted_guild_scheduled_events = 30038,
err_stickers = 30039,
err_prune_requests = 30040,
err_guild_widget_settings_updates = 30042,
err_edits_to_messages_older_than_1_hour = 30046,
err_pinned_threads_in_a_forum_channel = 30047,
err_tags_in_a_forum_channel = 30048,
err_bitrate_is_too_high_for_channel_of_this_type = 30052,
err_premium_emojis = 30056,
err_webhooks_per_guild = 30058,
err_channel_permission_overwrites = 30060,
err_the_channels_for_this_guild_are_too_large = 30061,
err_unauthorized_invalid_token = 40001,
err_verify_your_account = 40002,
err_you_are_opening_direct_messages_too_fast = 40003,
err_send_messages_has_been_temporarily_disabled = 40004,
err_request_entity_too_large = 40005,
err_this_feature_has_been_temporarily_disabled_server_side = 40006,
err_the_user_is_banned_from_this_guild = 40007,
err_connection_has_been_revoked = 40012,
err_target_user_is_not_connected_to_voice = 40032,
err_this_message_has_already_been_crossposted = 40033,
err_an_application_command_with_that_name_already_exists = 40041,
err_application_interaction_failed_to_send = 40043,
err_cannot_send_a_message_in_a_forum_channel = 40058,
err_interaction_has_already_been_acknowledged = 40060,
err_tag_names_must_be_unique = 40061,
err_service_resource_is_being_rate_limited = 40062,
err_no_tags_available = 40066,
err_tag_required = 40067,
err_entitlement_already_granted = 40074,
err_missing_access = 50001,
err_invalid_account_type = 50002,
err_cannot_execute_action_on_a_dm_channel = 50003,
err_guild_widget_disabled = 50004,
err_cannot_edit_a_message_by_other_user = 50005,
err_cannot_send_empty_message = 50006,
err_cannot_send_messages_to_this_user = 50007,
err_cannot_send_messages_in_a_non_text_channel = 50008,
err_channel_verification_level_too_high = 50009,
err_oauth2_application_does_not_have_a_bot = 50010,
err_oauth2_application_limit = 50011,
err_invalid_oauth2_state = 50012,
err_permissions = 50013,
err_invalid_authentication_token = 50014,
err_note_was_too_long = 50015,
err_too_few_or_too_many_messages = 50016,
err_invalid_mfa_level = 50017,
err_invalid_pin = 50019,
err_invite_code_invalid = 50020,
err_system_message = 50021,
err_channel_type = 50024,
err_invalid_oauth2_access_token = 50025,
err_missing_required_oauth2_scope = 50026,
err_invalid_webhook_token = 50027,
err_invalid_role = 50028,
err_invalid_recipients = 50033,
err_too_old_to_bulk_delete = 50034,
err_invalid_form_body = 50035,
err_invite_error = 50036,
err_invalid_activity_action = 50039,
err_invalid_api_version_provided = 50041,
err_file_uploaded_exceeds_the_maximum_size = 50045,
err_invalid_file_uploaded = 50046,
err_cannot_self_redeem_this_gift = 50054,
err_invalid_guild = 50055,
err_invalid_sku = 50057,
err_invalid_request_origin = 50067,
err_invalid_message_type = 50068,
err_payment_source_required = 50070,
err_cannot_modify_a_system_webhook = 50073,
err_cannot_delete_a_channel_required_for_community_guilds = 50074,
err_cannot_edit_stickers_within_a_message = 50080,
err_invalid_sticker_sent = 50081,
err_tried_to_perform_an_operation_on_an_archived_thread = 50083,
err_invalid_thread_notification_settings = 50084,
err_before_value_is_earlier_than_the_thread_creation_date = 50085,
err_community_server_channels_must_be_text_channels = 50086,
err_bad_event_entity_type = 50091,
err_this_server_is_not_available_in_your_location = 50095,
err_monetization_enabled_in_order_to_perform_this_action = 50097,
err_more_boosts_to_perform_this_action = 50101,
err_the_request_body_contains_invalid_json = 50109,
err_owner_cannot_be_pending_member = 50131,
err_ownership_cannot_be_transferred_to_a_bot_user = 50132,
err_failed_to_resize_asset_below_the_maximum_size = 50138,
err_cannot_mix_subscription_and_non_subscription_roles_for_an_emoji = 50144,
err_cannot_convert_between_premium_emoji_and_normal_emoji = 50145,
err_uploaded_file_not_found = 50146,
err_voice_messages_do_not_support_additional_content = 50159,
err_voice_messages_must_have_a_single_audio_attachment = 50160,
err_voice_messages_must_have_supporting_metadata = 50161,
err_voice_messages_cannot_be_edited = 50162,
err_cannot_delete_guild_subscription_integration = 50163,
err_you_cannot_send_voice_messages_in_this_channel = 50173,
err_the_user_account_must_first_be_verified = 50178,
err_you_do_not_have_permission_to_send_this_sticker = 50600,
err_two_factor_is_required_for_this_operation = 60003,
err_no_users_with_discordtag_exist = 80004,
err_reaction_was_blocked = 90001,
err_user_cannot_use_burst_reactions = 90002,
err_application_not_yet_available = 110001,
err_api_resource_is_currently_overloaded = 130000,
err_the_stage_is_already_open = 150006,
err_cannot_reply_without_permission_to_read_message_history = 160002,
err_a_thread_has_already_been_created_for_this_message = 160004,
err_thread_is_locked = 160005,
err_active_threads = 160006,
err_active_announcement_threads = 160007,
err_invalid_json_for_uploaded_lottie_file = 170001,
err_uploaded_lotties_cannot_contain_rasterized_images = 170002,
err_sticker_maximum_framerate = 170003,
err_sticker_frame_count = 170004,
err_lottie_animation_dimensions = 170005,
err_sticker_frame_rate = 170006,
err_sticker_animation_duration = 170007,
err_cannot_update_a_finished_event = 180000,
err_failed_to_create_stage_needed_for_stage_event = 180002,
err_message_was_blocked_by_automatic_moderation = 200000,
err_title_was_blocked_by_automatic_moderation = 200001,
err_webhooks_posted_to_forum_channels_must_have_a_thread_name_or_thread_id = 220001,
err_webhooks_posted_to_forum_channels_cannot_have_both_a_thread_name_and_thread_id = 220002,
err_webhooks_can_only_create_threads_in_forum_channels = 220003,
err_webhook_services_cannot_be_used_in_forum_channels = 220004,
err_message_blocked_links = 240000,
err_cannot_enable_onboarding_requirements_are_not_met = 350000,
err_cannot_update_onboarding_below_requirements = 350001,
};
/**
* @brief The dpp::exception class derives from std::exception and supports some other
* ways of passing in error details such as via std::string.
*/
class exception : public std::exception
{
protected:
/**
* @brief Exception message
*/
std::string msg;
/**
* @brief Exception error code
*/
exception_error_code error_code;
public:
using std::exception::exception;
/**
* @brief Construct a new exception object
*/
exception() = default;
/**
* @brief Construct a new exception object
*
* @param what reason message
*/
explicit exception(const char* what) : msg(what), error_code(err_no_code_specified) { }
/**
* @brief Construct a new exception object
*
* @param what reason message
* @param code Exception code
*/
explicit exception(exception_error_code code, const char* what) : msg(what), error_code(code) { }
/**
* @brief Construct a new exception object
*
* @param what reason message
* @param len length of reason message
*/
exception(const char* what, size_t len) : msg(what, len), error_code(err_no_code_specified) { }
/**
* @brief Construct a new exception object
*
* @param what reason message
*/
explicit exception(const std::string& what) : msg(what), error_code(err_no_code_specified) { }
/**
* @brief Construct a new exception object
*
* @param what reason message
* @param code Exception code
*/
explicit exception(exception_error_code code, const std::string& what) : msg(what), error_code(code) { }
/**
* @brief Construct a new exception object
*
* @param what reason message
*/
explicit exception(std::string&& what) : msg(std::move(what)) { }
/**
* @brief Construct a new exception object
*
* @param what reason message
* @param code Exception code
*/
explicit exception(exception_error_code code, std::string&& what) : msg(std::move(what)), error_code(code) { }
/**
* @brief Construct a new exception object (copy constructor)
*/
exception(const exception&) = default;
/**
* @brief Construct a new exception object (move constructor)
*/
exception(exception&&) = default;
/**
* @brief Destroy the exception object
*/
~exception() override = default;
/**
* @brief Copy assignment operator
*
* @return exception& reference to self
*/
exception & operator = (const exception &) = default;
/**
* @brief Move assignment operator
*
* @return exception& reference to self
*/
exception & operator = (exception&&) = default;
/**
* @brief Get exception message
*
* @return const char* error message
*/
[[nodiscard]] const char* what() const noexcept override { return msg.c_str(); };
/**
* @brief Get exception code
*
* @return exception_error_code error code
*/
[[nodiscard]] exception_error_code code() const noexcept { return error_code; };
};
#ifndef _DOXYGEN_
#define derived_exception(name, ancestor) class name : public dpp::ancestor { \
public: \
using dpp::ancestor::ancestor; \
name() = default; \
explicit name(const char* what) : ancestor(what) { } \
explicit name(exception_error_code code, const char* what) : ancestor(code, what) { } \
name(const char* what, size_t len) : ancestor(what, len) { } \
explicit name(const std::string& what) : ancestor(what) { } \
explicit name(exception_error_code code, const std::string& what) : ancestor(code, what) { } \
explicit name(std::string&& what) : ancestor(what) { } \
explicit name(exception_error_code code, std::string&& what) : ancestor(code, what) { } \
name(const name&) = default; \
name(name&&) = default; \
~name() override = default; \
name & operator = (const name &) = default; \
name & operator = (name&&) = default; \
[[nodiscard]] const char* what() const noexcept override { return msg.c_str(); }; \
[[nodiscard]] exception_error_code code() const noexcept { return error_code; }; \
};
#endif
#ifdef _DOXYGEN_
/*
* THESE DEFINITIONS ARE NOT THE REAL DEFINITIONS USED BY PROGRAM CODE.
*
* They exist only to cause Doxygen to emit proper documentation for the derived exception types.
* Proper definitions are emitted by the `derived_exception` macro in the "else" section.
*/
/**
* @brief Represents an error in logic, e.g. you asked the library to do something the Discord API does not support
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class logic_exception : public dpp::exception { };
/**
* @brief Represents an error reading or writing to a file
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class file_exception : public dpp::exception { };
/**
* @brief Represents an error establishing or maintaining a connection
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class connection_exception : public dpp::exception { };
/**
* @brief Represents an error with voice processing
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class voice_exception : public dpp::exception { };
/**
* @brief Represents an error on a REST API call, e.g. a HTTPS request
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class rest_exception : public dpp::exception { };
/**
* @brief Represents invalid length of argument being passed to a function
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class length_exception : public dpp::exception { };
/**
* @brief Represents inability to parse data, usually caused by malformed JSON or ETF
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class parse_exception : public dpp::exception { };
/**
* @brief Represents invalid access to dpp's cache or its members, which may or may not exist.
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class cache_exception : public dpp::exception { };
/**
* @brief Represents an attempt to construct a cluster with an invalid bot token.
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class invalid_token_exception : public dpp::rest_exception { };
#ifndef DPP_NO_CORO
/**
* @brief Represents the cancellation of a task. Will be thrown to the awaiter of a cancelled task.
* @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception.
*/
class task_cancelled_exception : public dpp::exception { };
#endif /* DPP_NO_CORO */
#else
derived_exception(logic_exception, exception);
derived_exception(file_exception, exception);
derived_exception(connection_exception, exception);
derived_exception(voice_exception, exception);
derived_exception(encryption_exception, voice_exception);
derived_exception(decryption_exception, voice_exception);
derived_exception(rest_exception, exception);
derived_exception(invalid_token_exception, rest_exception);
derived_exception(length_exception, exception);
derived_exception(parse_exception, exception);
derived_exception(cache_exception, exception);
# ifndef DPP_NO_CORO
derived_exception(task_cancelled_exception, exception);
# endif /* DPP_NO_CORO */
#endif
}

148
include/dpp/export.h Normal file
View File

@ -0,0 +1,148 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
/* Compile-time check for C++17.
* Either one of the following causes a compile time error:
* __cplusplus not defined at all (this means we are being compiled on a C compiler)
* MSVC defined and _MSVC_LANG < 201703L (Visual Studio, but not C++17 or newer)
* MSVC not defined and __cplusplus < 201703L (Non-visual studio, but not C++17 or newer)
* The additional checks are required because MSVC doesn't correctly set __cplusplus to 201703L,
* which is hugely non-standard, but apparently "it broke stuff" so they dont ever change it
* from C++98. Ugh.
*/
#if (!defined(__cplusplus) || (defined(_MSC_VER) && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L)) || (!defined(_MSC_VER) && __cplusplus < 201703L))
#error "D++ Requires a C++17 compatible C++ compiler. Please ensure that you have enabled C++17 in your compiler flags."
#endif
/* If not using c++20, define DPP_CPP17_COMPAT and DPP_NO_CORO if DPP_NO_CORO is not already defined.
*/
#if !(defined(__cplusplus) && __cplusplus >= 202002L) && !(defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
#define DPP_CPP17_COMPAT
#if !defined(DPP_CORO) || !DPP_CORO // Allow overriding this because why not
#ifndef DPP_NO_CORO
#define DPP_NO_CORO
#endif
#endif
#endif
#ifndef DPP_STATIC
/* Dynamic linked build as shared object or dll */
#ifdef DPP_BUILD
/* Building the library */
#ifdef _WIN32
#include <dpp/win32_safe_warnings.h>
#define DPP_EXPORT __declspec(dllexport)
#else
#define DPP_EXPORT
#endif
#else
/* Including the library */
#ifdef _WIN32
#define DPP_EXPORT __declspec(dllimport)
#else
#define DPP_EXPORT
#endif
#endif
#else
/* Static linked build */
#if defined(_WIN32) && defined(DPP_BUILD)
#include <dpp/win32_safe_warnings.h>
#endif
#define DPP_EXPORT
#endif
namespace dpp {
/**
* @brief Represents a build configuration. On some platforms (e.g. Windows) release isn't compatible with debug, so we use this enum to detect it.
*/
enum class build_type {
/**
* @brief Universal build, works with both debug and release
*/
universal,
/**
* @brief Debug build
*/
debug,
/**
* @brief Release build
*/
release
};
template <build_type>
extern bool DPP_EXPORT validate_configuration();
#if defined(UE_BUILD_DEBUG) || defined(UE_BUILD_DEVELOPMENT) || defined(UE_BUILD_TEST) || defined(UE_BUILD_SHIPPING) || defined(UE_GAME) || defined(UE_EDITOR) || defined(UE_BUILD_SHIPPING_WITH_EDITOR) || defined(UE_BUILD_DOCS)
/*
* We need to tell DPP to NOT do the version checker if something from Unreal Engine is defined.
* We have to do this because UE is causing some weirdness where the version checker is broken and always errors.
* This is really only for DPP-UE. There is no reason to not do the version checker unless you are in Unreal Engine.
*/
#define DPP_BYPASS_VERSION_CHECKING
#endif /* UE */
#ifndef DPP_BUILD /* when including dpp */
/**
* Version checking, making sure the program is in a configuration compatible with DPP's.
*
* Do NOT make these variables constexpr.
* We want them to initialize at runtime so the function can be pulled from the shared library object.
*/
#ifndef DPP_BYPASS_VERSION_CHECKING
#if defined(_WIN32)
#ifdef _DEBUG
inline const bool is_valid_config = validate_configuration<build_type::debug>();
#else
inline const bool is_valid_config = validate_configuration<build_type::release>();
#endif /* _DEBUG */
#else
inline const bool is_valid_config = validate_configuration<build_type::universal>();
#endif /* _WIN32 */
#endif /* !DPP_BYPASS_VERSION_CHECKING */
#endif /* !DPP_BUILD */
}
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#endif
#ifdef _DOXYGEN_
/** @brief Macro that expands to [[deprecated(reason)]] when including the library, nothing when building the library */
#define DPP_DEPRECATED(reason)
#else /* !_DOXYGEN_ */
#if defined(DPP_BUILD) || defined(DPP_NO_DEPRECATED)
/** @brief Macro that expands to [[deprecated(reason)]] when including the library, nothing when building the library */
#define DPP_DEPRECATED(reason)
#else
/** @brief Macro that expands to [[deprecated(reason)]] when including the library, nothing when building the library */
#define DPP_DEPRECATED(reason) [[deprecated(reason)]]
#endif
#endif /* _DOXYGEN_ */

2053
include/dpp/guild.h Normal file

File diff suppressed because it is too large Load Diff

74
include/dpp/http_server.h Normal file
View File

@ -0,0 +1,74 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/cluster.h>
#include <dpp/socket_listener.h>
#include <dpp/http_server_request.h>
#include <dpp/ssl_context.h>
namespace dpp {
/**
* @brief Creates a simple HTTP server which listens on a TCP port for a
* plaintext or SSL incoming request, and passes that request to a callback
* to generate the response.
*/
struct DPP_EXPORT http_server : public socket_listener<http_server_request> {
/**
* @brief Request handler callback to use for all incoming HTTP(S) requests
*/
http_server_request_event request_handler;
/**
* @brief Port we are listening on
*/
uint16_t bound_port;
/**
* @brief Constructor for creation of a HTTP(S) server
* @param creator Cluster creator
* @param address address to bind to, use "0.0.0.0" to bind to all local addresses
* @param port port to bind to. You should generally use a port > 1024.
* @param handle_request Callback to call for each pending request
* @param private_key Private key PEM file for HTTPS/SSL. If empty, a plaintext server is created
* @param public_key Public key PEM file for HTTPS/SSL. If empty, a plaintext server is created
*/
http_server(cluster* creator, const std::string_view address, uint16_t port, http_server_request_event handle_request, const std::string& private_key = "", const std::string& public_key = "");
/**
* @brief Emplace a new request into the connection pool
* @param newfd file descriptor of new request
*/
void emplace(socket newfd) override;
/**
* @brief Destructor
*/
virtual ~http_server() {
detail::release_ssl_context(bound_port);
}
};
}

View File

@ -0,0 +1,260 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <map>
#include <list>
#include <vector>
#include <variant>
#include <dpp/sslconnection.h>
#include <dpp/version.h>
#include <dpp/stringops.h>
#include <dpp/httpsclient.h>
#include <climits>
namespace dpp {
/**
* @brief Callback type for HTTP server request callbacks
*/
using http_server_request_event = std::function<void(class http_server_request*)>;
/*
* @brief Implements a HTTPS socket client based on the SSL client.
* @note plaintext HTTP without SSL is also supported via a "downgrade" setting
*/
class DPP_EXPORT http_server_request : public ssl_connection {
/**
* @brief The request body, e.g. form data
*/
std::string request_body;
/**
* @brief Headers from the client
*/
http_headers request_headers;
/**
* @brief Time at which the request should be abandoned
*/
time_t timeout;
/**
* @brief Headers for our response
*/
http_headers response_headers;
/**
* @brief Handler to handle the inbound request
*/
http_server_request_event handler{};
/**
* @brief Response body
*/
std::string response_body;
protected:
/**
* @brief The type of the request, e.g. GET, POST
*/
std::string request_type;
/**
* @brief Path part of URL for HTTPS connection
*/
std::string path;
/**
* @brief Current connection state
*/
http_state state;
/**
* @brief HTTP status code for response
*/
uint16_t status{0};
/**
* @brief Start the connection
*/
virtual void connect() override;
/**
* @brief Called when the output buffer is drained to empty
*/
void on_buffer_drained() override;
/**
* @brief Maximum size of POST body
*/
[[nodiscard]] uint64_t get_max_post_size() const;
/**
* @brief Maximum size of headers
*/
[[nodiscard]] uint64_t get_max_header_size() const;
/**
* @brief Reply with an error message
* @param error_code error code
* @param message message
*/
void generate_error(uint16_t error_code, const std::string& message);
public:
/**
* @brief If true the response timed out while waiting
*/
bool timed_out;
/**
* @brief Get request state
* @return request state
*/
http_state get_state() const;
/**
* @brief Get current response body
* @return response body
*/
std::string get_response_body() const;
/**
* @brief Get current request body
* @return request body
*/
std::string get_request_body() const;
/**
* @brief Get current status code
* @return status code
*/
uint16_t get_status() const;
/**
* @brief Content length sent by client
*/
uint64_t content_length{ULLONG_MAX};
/**
* @brief Construct a new server request object.
* Server request objects are instantiated for an incoming server connection, as such they already
* have a file descriptor.
* @param creator creating owner
* @param fd file descriptor
* @param port Port the connection came in on
* @param plaintext_downgrade true if plaintext, false if SSL
* @param private_key if SSL, the path to the private key PEM
* @param public_key if SSL, the path to the public key PEM
* @param handle_request request handler callback
*/
http_server_request(cluster* creator, socket fd, uint16_t port, bool plaintext_downgrade, const std::string& private_key, const std::string& public_key, http_server_request_event handle_request);
/**
* @brief Destroy the https client object
*/
virtual ~http_server_request() override;
/**
* @brief Processes incoming data from the SSL socket input buffer.
*
* @param buffer The buffer contents. Can modify this value removing the head elements when processed.
*/
virtual bool handle_buffer(std::string &buffer) override;
/**
* @brief Close HTTPS socket
*/
virtual void close() override;
/**
* @brief Fires every second from the underlying socket I/O loop, used for timeouts
*/
virtual void one_second_timer() override;
/**
* @brief Get a HTTP request header
*
* @param header_name Header name to find, case insensitive
* @return Header content or empty string if not found.
* If multiple values have the same header_name, this will return one of them.
* @see get_header_count to determine if multiple are present
* @see get_header_list to retrieve all entries of the same header_name
*/
[[nodiscard]] const std::string get_header(const std::string& header_name) const;
/**
* @brief Get the number of headers with the same header name
*
* @param header_name
* @return the number of headers with this count
*/
[[nodiscard]] size_t get_header_count(const std::string& header_name) const;
/**
* @brief Get a set of HTTP request headers with a common name
*
* @param header_name
* @return A list of headers with the same name, or an empty list if not found
*/
[[nodiscard]] std::list<std::string> get_header_list(const std::string& header_name) const;
/**
* @brief Get all HTTP request headers
*
* @return headers as a map
*/
[[nodiscard]] std::multimap<std::string, std::string> get_headers() const;
/**
* @brief Set a response header
* @param header header name
* @param value header value
* @return ref to self
*/
http_server_request& set_response_header(const std::string& header, const std::string& value);
/**
* @brief Set the response content
*
* @param new_content response content
*/
http_server_request& set_response_body(const std::string& new_content);
/**
* @brief Set the response HTTP status, e.g.
* 200 for OK, 404 for not found, 429 for rate limited etc.
*
* @param new_status HTTP status
*/
http_server_request& set_status(uint16_t new_status);
/**
* @brief Get whole response as a string
*/
[[nodiscard]] std::string get_response();
};
}

357
include/dpp/httpsclient.h Normal file
View File

@ -0,0 +1,357 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <map>
#include <list>
#include <vector>
#include <variant>
#include <dpp/sslconnection.h>
#include <dpp/version.h>
#include <dpp/stringops.h>
namespace dpp {
static inline const std::string http_version = "DiscordBot (https://github.com/brainboxdotcc/DPP, "
+ to_hex(DPP_VERSION_MAJOR, false) + "." + to_hex(DPP_VERSION_MINOR, false) + "." + to_hex(DPP_VERSION_PATCH, false) + ")";
static inline constexpr const char* DISCORD_HOST = "https://discord.com";
/**
* @brief HTTP connection status
*/
enum http_state : uint8_t {
/**
* @brief Sending/receiving HTTP headers and request body
*/
HTTPS_HEADERS,
/**
* @brief Receiving body content.
*/
HTTPS_CONTENT,
/**
* @brief Completed connection, as it was closed or the body is >= Content-Length
*
*/
HTTPS_DONE,
/**
* @brief Received chunk length
*
*/
HTTPS_CHUNK_LEN,
/**
* @brief Received chunk trailing CRLF
*/
HTTPS_CHUNK_TRAILER,
/**
* @brief The last received chunk is the final chunk
*/
HTTPS_CHUNK_LAST,
/**
* @brief Receiving contents of a chunk
*/
HTTPS_CHUNK_CONTENT
};
/**
* @brief Request headers
*/
typedef std::multimap<std::string, std::string> http_headers;
/**
* @brief Represents a multipart mime body and the correct top-level mime type
* If a non-multipart request is passed in, this is represented as a plain body
* and the application/json mime type.
*/
struct multipart_content {
/**
* @brief Multipart body
*/
std::string body;
/**
* @brief MIME type
*/
std::string mimetype;
};
/**
* @brief Represents a HTTP scheme, hostname and port
* split into parts for easy use in https_client.
*/
struct http_connect_info {
/**
* @brief True if the connection should be SSL
*/
bool is_ssl{};
/**
* @brief The request scheme, e.g. 'https' or 'http'
*/
std::string scheme;
/**
* @brief The request hostname part, e.g. 'discord.com'
*/
std::string hostname;
/**
* @brief The port number, either determined from the scheme,
* or from the part of the hostname after a colon ":" character
*/
uint16_t port{};
};
using https_client_completion_event = std::function<void(class https_client*)>;
/**
* @brief Implements a HTTPS socket client based on the SSL client.
* @note plaintext HTTP without SSL is also supported via a "downgrade" setting
*/
class DPP_EXPORT https_client : public ssl_connection {
/**
* @brief The type of the request, e.g. GET, POST
*/
std::string request_type;
/**
* @brief Path part of URL for HTTPS connection
*/
std::string path;
/**
* @brief The request body, e.g. form data
*/
std::string request_body;
/**
* @brief The response body, e.g. file content or JSON
*/
std::string body;
/**
* @brief The reported length of the content. If this is
* UULONG_MAX, then no length was reported by the server.
*/
uint64_t content_length;
/**
* @brief Headers for the request, e.g. Authorization, etc.
*/
http_headers request_headers;
/**
* @brief The status of the HTTP request from the server,
* e.g. 200 for OK, 404 for not found. A value of 0 means
* no request has been completed.
*/
uint16_t status;
/**
* @brief The HTTP protocol to use
*/
std::string http_protocol;
/**
* @brief Time at which the request should be abandoned
*/
time_t timeout;
/**
* @brief If true the content is chunked encoding
*/
bool chunked;
/**
* @brief Size of current chunk
*/
size_t chunk_size;
/**
* @brief Number of bytes received in current chunk
*/
size_t chunk_receive;
/**
* @brief Headers from the server's response, e.g. RateLimit
* headers, cookies, etc.
*/
std::multimap<std::string, std::string> response_headers;
protected:
/**
* @brief Start the connection
*/
virtual void connect() override;
/**
* @brief Get request state
* @return request state
*/
http_state get_state();
public:
/**
* @brief If true the response timed out while waiting
*/
bool timed_out;
/**
* @brief Function to call when HTTP request is completed
*/
https_client_completion_event completed;
/**
* @brief Current connection state
*/
http_state state;
/**
* @brief Connect to a specific HTTP(S) server and complete a request.
*
* The constructor will attempt the connection, and return the content.
* By the time the constructor completes, the HTTP request will be stored
* in the object.
*
* @note This is a blocking call. It starts a loop which runs non-blocking
* functions within it, but does not return until the request completes.
* See queues.cpp for how to make this asynchronous.
*
* @param hostname Hostname to connect to
* @param port Port number to connect to, usually 443 for SSL and 80 for plaintext
* @param urlpath path part of URL, e.g. "/api"
* @param verb Request verb, e.g. GET or POST
* @param req_body Request body, use dpp::https_client::build_multipart() to build a multipart MIME body (e.g. for multiple file upload)
* @param extra_headers Additional request headers, e.g. user-agent, authorization, etc
* @param plaintext_connection Set to true to make the connection plaintext (turns off SSL)
* @param request_timeout How many seconds before the connection is considered failed if not finished
* @param protocol Request HTTP protocol (default: 1.1)
* @param done Function to call when https_client request is completed
*/
https_client(cluster* creator, const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1", https_client_completion_event done = {});
/**
* @brief Destroy the https client object
*/
virtual ~https_client() override;
/**
* @brief Build a multipart content from a set of files and some json
*
* @param json The json content
* @param filenames File names of files to send
* @param contents Contents of each of the files to send
* @param mimetypes MIME types of each of the files to send
* @return multipart mime content and headers
*/
static multipart_content build_multipart(const std::string &json, const std::vector<std::string>& filenames = {}, const std::vector<std::string>& contents = {}, const std::vector<std::string>& mimetypes = {});
/**
* @brief Processes incoming data from the SSL socket input buffer.
*
* @param buffer The buffer contents. Can modify this value removing the head elements when processed.
*/
virtual bool handle_buffer(std::string &buffer) override;
/**
* @brief Close HTTPS socket
*/
virtual void close() override;
/**
* @brief Fires every second from the underlying socket I/O loop, used for timeouts
*/
virtual void one_second_timer() override;
/**
* @brief Get a HTTP response header
*
* @param header_name Header name to find, case insensitive
* @return Header content or empty string if not found.
* If multiple values have the same header_name, this will return one of them.
* @see get_header_count to determine if multiple are present
* @see get_header_list to retrieve all entries of the same header_name
*/
const std::string get_header(std::string header_name) const;
/**
* @brief Get the number of headers with the same header name
*
* @param header_name
* @return the number of headers with this count
*/
size_t get_header_count(std::string header_name) const;
/**
* @brief Get a set of HTTP response headers with a common name
*
* @param header_name
* @return A list of headers with the same name, or an empty list if not found
*/
const std::list<std::string> get_header_list(std::string header_name) const;
/**
* @brief Get all HTTP response headers
*
* @return headers as a map
*/
const std::multimap<std::string, std::string> get_headers() const;
/**
* @brief Get the response content
*
* @return response content
*/
const std::string get_content() const;
/**
* @brief Get the response HTTP status, e.g.
* 200 for OK, 404 for not found, 429 for rate limited.
* A value of 0 indicates the request was not completed.
*
* @return uint16_t HTTP status
*/
uint16_t get_status() const;
/**
* @brief Break down a scheme, hostname and port into
* a http_connect_info.
*
* All but the hostname portion are optional. The path component
* should not be passed to this function.
*
* @param url URL to break down
* @return Split URL
*/
static http_connect_info get_host_info(std::string url);
};
}

353
include/dpp/integration.h Normal file
View File

@ -0,0 +1,353 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/json_fwd.h>
#include <unordered_map>
#include <dpp/json_interface.h>
#include <dpp/user.h>
namespace dpp {
/**
* @brief Where an app can be installed, also called its supported installation contexts.
*/
enum application_integration_types : uint8_t {
/**
* @brief Installable to servers
*/
ait_guild_install = 0,
/**
* @brief Installable to users
*/
ait_user_install = 1,
};
/**
* @brief Integration types
*/
enum integration_type : uint8_t {
/**
* @brief Twitch integration
*/
i_twitch,
/**
* @brief YouTube integration
*/
i_youtube,
/**
* @brief Discord integration
*/
i_discord,
/**
* @brief Subscription
*/
i_guild_subscription,
};
/**
* @brief Integration flags
*/
enum integration_flags : uint8_t {
/**
* @brief Is this integration enabled?
*/
if_enabled = 0b00000001,
/**
* @brief Is this integration syncing?
* @warning This is not provided for discord bot integrations.
*/
if_syncing = 0b00000010,
/**
* @brief Whether emoticons should be synced for this integration (twitch only currently).
* @warning This is not provided for discord bot integrations.
*/
if_emoticons = 0b00000100,
/**
* @brief Has this integration been revoked?
* @warning This is not provided for discord bot integrations.
*/
if_revoked = 0b00001000,
/**
* @brief Kick user when their subscription expires, otherwise only remove the role that is specified by `role_id`.
* @warning This is not provided for discord bot integrations.
*/
if_expire_kick = 0b00010000,
};
/**
* @brief An application that has been integrated
*/
struct DPP_EXPORT integration_app {
/**
* @brief The id of the app.
*/
snowflake id;
/**
* @brief The name of the app.
*/
std::string name;
/**
* @brief The icon hash of the app.
*/
utility::iconhash icon;
/**
* @brief The description of the app
*/
std::string description;
/**
* @brief The bot associated with this application.
*/
class user* bot;
};
/**
* @brief The account information for an integration.
*/
struct DPP_EXPORT integration_account {
/**
* @brief ID of the account
*/
snowflake id;
/**
* @brief Name of the account.
*/
std::string name;
};
/**
* @brief Represents an integration on a guild, e.g. a connection to twitch.
*/
class DPP_EXPORT integration : public managed, public json_interface<integration> {
protected:
friend struct json_interface<integration>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
integration& fill_from_json_impl(nlohmann::json* j);
/** Build a json from this object.
* @param with_id Add ID to output
* @return JSON of the object
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Integration name.
*/
std::string name;
/**
* @brief Integration type (twitch, youtube, discord, or guild_subscription).
*/
integration_type type;
/**
* @brief Integration flags from dpp::integration_flags
*/
uint8_t flags;
/**
* @brief ID that this integration uses for "subscribers".
*
* @warning This is not provided for discord bot integrations.
*/
snowflake role_id;
/**
* @brief The grace period (in days) before expiring subscribers.
*
* @warning This is not provided for discord bot integrations.
*/
uint32_t expire_grace_period;
/**
* @brief User for this integration
*/
user user_obj;
/**
* @brief Integration account information
*/
integration_account account;
/**
* @brief When this integration was last synced.
*
* @warning This is not provided for discord bot integrations.
*/
time_t synced_at;
/**
* @brief How many subscribers this integration has.
*
* @warning This is not provided for discord bot integrations.
*/
uint32_t subscriber_count;
/**
* @brief The bot/OAuth2 application for discord integrations.
*/
integration_app app;
/**
* @brief The scopes the application has been authorized for.
*/
std::vector<std::string> scopes;
/** Default constructor */
integration();
/** Default destructor */
~integration() = default;
/**
* Are emoticons enabled for this integration?
* @warning This is not provided for discord bot integrations.
*/
bool emoticons_enabled() const;
/**
* Is the integration enabled?
* @warning This is not provided for discord bot integrations.
*/
bool is_enabled() const;
/**
* Is the integration syncing?
* @warning This is not provided for discord bot integrations.
*/
bool is_syncing() const;
/**
* Has this integration been revoked?
* @warning This is not provided for discord bot integrations.
*/
bool is_revoked() const;
/**
* Will the user be kicked if their subscription runs out to the integration?
* If false, the integration will simply remove the role that is specified by `role_id`.
* @warning This is not provided for discord bot integrations.
*/
bool expiry_kicks_user() const;
};
/**
* @brief The connection object that the user has attached.
*/
class DPP_EXPORT connection : public json_interface<connection> {
protected:
friend struct json_interface<connection>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
connection& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief ID of the connection account.
*/
std::string id;
/**
* @brief the username of the connection account.
*/
std::string name;
/**
* @brief the service of the connection (twitch, youtube, discord, or guild_subscription).
*/
std::string type;
/**
* @brief Optional: whether the connection is revoked.
*/
bool revoked;
/**
* @brief Optional: an array of partial server integrations.
*/
std::vector<integration> integrations;
/**
* @brief Whether the connection is verified.
*/
bool verified;
/**
* @brief Whether friend sync is enabled for this connection.
*/
bool friend_sync;
/**
* @brief Whether activities related to this connection will be shown in presence updates.
*/
bool show_activity;
/**
* @brief Whether this connection has a corresponding third party OAuth2 token.
*/
bool two_way_link;
/**
* @brief Visibility of this connection.
*/
bool visible;
/**
* @brief Construct a new connection object
*/
connection();
};
/**
* @brief A group of integrations
*/
typedef std::unordered_map<snowflake, integration> integration_map;
/**
* @brief A group of connections
*/
typedef std::unordered_map<snowflake, connection> connection_map;
}

155
include/dpp/intents.h Normal file
View File

@ -0,0 +1,155 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
namespace dpp {
/**
* @brief intents are a bitmask of allowed events on your websocket.
*
* Some of these are known as Privileged intents (GUILD_MEMBERS and GUILD_PRESENCES)
* and require verification of a bot over 100 servers by discord via submission of
* your real life ID.
*/
enum intents {
/**
* @brief Intent for receipt of guild information.
*/
i_guilds = (1 << 0),
/**
* @brief Intent for receipt of guild members.
*/
i_guild_members = (1 << 1),
/**
* @brief Intent for receipt of guild bans.
*/
i_guild_bans = (1 << 2),
/**
* @brief Intent for receipt of guild emojis.
*/
i_guild_emojis = (1 << 3),
/**
* @brief Intent for receipt of guild integrations.
*/
i_guild_integrations = (1 << 4),
/**
* @brief Intent for receipt of guild webhooks.
*/
i_guild_webhooks = (1 << 5),
/**
* @brief Intent for receipt of guild invites.
*/
i_guild_invites = (1 << 6),
/**
* @brief Intent for receipt of guild voice states.
*/
i_guild_voice_states = (1 << 7),
/**
* @brief Intent for receipt of guild presences.
*/
i_guild_presences = (1 << 8),
/**
* @brief Intent for receipt of guild messages.
*/
i_guild_messages = (1 << 9),
/**
* @brief Intent for receipt of guild message reactions.
*/
i_guild_message_reactions = (1 << 10),
/**
* @brief Intent for receipt of guild message typing notifications.
*/
i_guild_message_typing = (1 << 11),
/**
* @brief Intent for receipt of direct messages (DMs).
*/
i_direct_messages = (1 << 12),
/**
* @brief Intent for receipt of direct message reactions.
*/
i_direct_message_reactions = (1 << 13),
/**
* @brief Intent for receipt of direct message typing notifications.
*/
i_direct_message_typing = (1 << 14),
/**
* @brief Intent for receipt of message content.
*/
i_message_content = (1 << 15),
/**
* @brief Scheduled events.
*/
i_guild_scheduled_events = (1 << 16),
/**
* @brief Auto moderation configuration.
*/
i_auto_moderation_configuration = (1 << 20),
/**
* @brief Auto moderation configuration.
*/
i_auto_moderation_execution = (1 << 21),
/**
* @brief Default D++ intents (all non-privileged intents).
*/
i_default_intents = dpp::i_guilds | dpp::i_guild_bans | dpp::i_guild_emojis | dpp::i_guild_integrations |
dpp::i_guild_webhooks | dpp::i_guild_invites | dpp::i_guild_voice_states |
dpp::i_guild_messages | dpp::i_guild_message_reactions | dpp::i_guild_message_typing |
dpp::i_direct_messages | dpp::i_direct_message_typing | dpp::i_direct_message_reactions |
dpp::i_guild_scheduled_events | dpp::i_auto_moderation_configuration |
dpp::i_auto_moderation_execution,
/**
* @brief Privileged intents requiring ID.
*/
i_privileged_intents = dpp::i_guild_members | dpp::i_guild_presences | dpp::i_message_content,
/**
* @brief Every single intent (dpp::i_default_intents and dpp::i_privileged_intents).
*/
i_all_intents = dpp::i_default_intents | dpp::i_privileged_intents,
/**
* @brief Unverified bots default intents.
*/
i_unverified_default_intents = dpp::i_default_intents | dpp::i_message_content
};
}

245
include/dpp/invite.h Normal file
View File

@ -0,0 +1,245 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
#include <dpp/stage_instance.h>
#include <unordered_map>
#include <dpp/json_interface.h>
#include <dpp/channel.h>
#include <dpp/user.h>
#include <dpp/guild.h>
namespace dpp {
/**
* @brief Invite target types for dpp::invite
*/
enum invite_target_t : uint8_t {
/**
* @brief Undefined invite target type.
*/
itt_none = 0,
/**
* @brief Stream target type.
*/
itt_stream = 1,
/**
* @brief Embedded Application target type.
*/
itt_embedded_application = 2,
};
/**
* @brief Represents an invite to a discord guild or channel
*/
class DPP_EXPORT invite : public json_interface<invite> {
protected:
friend struct json_interface<invite>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
invite& fill_from_json_impl(nlohmann::json* j);
/** Build JSON from this object.
* @param with_id Include ID in JSON
* @return The JSON of the invite
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Invite code.
*/
std::string code;
/**
* @brief Readonly expiration timestamp of this invite or 0 if the invite doesn't expire.
* @note Only returned from cluster::invite_get
*/
time_t expires_at;
/**
* @brief Guild ID this invite is for.
*/
snowflake guild_id;
/**
* @brief The partial guild this invite is for.
* @note Only filled in retrieved invites.
*/
guild destination_guild;
/**
* @brief Channel ID this invite is for.
*/
snowflake channel_id;
/**
* @brief The partial channel this invite is for.
* @note Only filled in retrieved invites.
*/
channel destination_channel;
/**
* @brief User ID who created this invite.
* @deprecated Use the `inviter` field instead
*/
snowflake inviter_id;
/**
* @brief User who created this invite.
*/
user inviter;
/**
* @brief The user ID whose stream to display for this voice channel stream invite.
*/
snowflake target_user_id;
/**
* @brief Target type for this voice channel invite.
*/
invite_target_t target_type;
/**
* @brief Approximate number of online users.
* @note Only returned from cluster::invite_get
*/
uint32_t approximate_presence_count;
/**
* @brief Approximate number of total users online and offline.
* @note Only returned from cluster::invite_get.
*/
uint32_t approximate_member_count;
/**
* @brief Duration (in seconds) after which the invite expires, or 0 for no expiration. Defaults to 86400 (1 day).
*
* @note Must be between 0 and 604800 (7 days).
*/
uint32_t max_age;
/**
* @brief Maximum number of uses, or 0 for unlimited. Defaults to 0.
*
* @note Must be between 0 and 100.
*/
uint8_t max_uses;
/**
* @brief Whether this invite only grants temporary membership.
*/
bool temporary;
/**
* @brief True if this invite should not replace or "attach to" similar invites.
*/
bool unique;
/**
* @brief How many times this invite has been used.
*/
uint32_t uses;
/**
* @note The stage instance data if there is a public stage instance in the stage channel this invite is for.
* @deprecated Deprecated
*/
stage_instance stage;
/**
* @brief Timestamp at which the invite was created.
*/
time_t created_at;
/**
* @brief Constructor.
*/
invite();
/**
* @brief Destructor.
*/
virtual ~invite() = default;
/**
* @brief Set the max age after which the invite expires
*
* @param max_age_ The duration in seconds, or 0 for no expiration. Must be between 0 and 604800 (7 days)
* @return invite& reference to self for chaining of calls
*/
invite& set_max_age(const uint32_t max_age_);
/**
* @brief Set the maximum number of uses for this invite
*
* @param max_uses_ Maximum number of uses, or 0 for unlimited. Must be between 0 and 100
* @return invite& reference to self for chaining of calls
*/
invite& set_max_uses(const uint8_t max_uses_);
/**
* @brief Set the target user id
*
* @param user_id The user ID whose stream to display for this voice channel stream invite
* @return invite& reference to self for chaining of calls
*/
invite& set_target_user_id(const snowflake user_id);
/**
* @brief Set the target type for this voice channel invite
*
* @param type invite_target_t Target type
* @return invite& reference to self for chaining of calls
*/
invite& set_target_type(const invite_target_t type);
/**
* @brief Set temporary property of this invite object
*
* @param is_temporary Whether this invite only grants temporary membership
* @return invite& reference to self for chaining of calls
*/
invite& set_temporary(const bool is_temporary);
/**
* @brief Set unique property of this invite object
*
* @param is_unique True if this invite should not replace or "attach to" similar invites
* @return invite& reference to self for chaining of calls
*/
invite& set_unique(const bool is_unique);
};
/**
* @brief A container of invites
*/
typedef std::unordered_map<std::string, invite> invite_map;
}

112
include/dpp/isa/avx.h Normal file
View File

@ -0,0 +1,112 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#if defined _MSC_VER || defined __GNUC__ || defined __clang__
#include <immintrin.h>
#include <numeric>
#include <cstdint>
#include <limits>
namespace dpp {
using avx_float = __m128;
/**
* @brief A class for audio mixing operations using AVX instructions.
*/
class audio_mixer {
public:
/**
* @brief The number of 32-bit values per CPU register.
*/
inline static constexpr int32_t byte_blocks_per_register{ 4 };
/**
* @brief Collect a single register worth of data from data_in, apply gain and increment, and store the result in data_out.
* This version uses AVX instructions.
*
* @param data_in Pointer to the input array of int32_t values.
* @param data_out Pointer to the output array of int16_t values.
* @param current_gain The gain to be applied to the elements.
* @param increment The increment value to be added to each element.
*/
inline void collect_single_register(int32_t* data_in, int16_t* data_out, float current_gain, float increment) {
avx_float current_samples_new{ _mm_mul_ps(gather_values(data_in),
_mm_add_ps(_mm_set1_ps(current_gain), _mm_mul_ps(_mm_set1_ps(increment), _mm_set_ps(0.0f, 1.0f, 2.0f, 3.0f)))) };
current_samples_new = _mm_blendv_ps(_mm_max_ps(current_samples_new, _mm_set1_ps(static_cast<float>(std::numeric_limits<int16_t>::min()))),
_mm_min_ps(current_samples_new, _mm_set1_ps(static_cast<float>(std::numeric_limits<int16_t>::max()))),
_mm_cmp_ps(current_samples_new, _mm_set1_ps(0.0f), _CMP_GE_OQ));
store_values(current_samples_new, data_out);
}
/**
* @brief Combine a register worth of elements from decoded_data and store the result in up_sampled_vector.
* This version uses AVX instructions.
*
* @param up_sampled_vector Pointer to the array of int32_t values.
* @param decoded_data Pointer to the array of int16_t values.
*/
inline void combine_samples(int32_t* up_sampled_vector, const int16_t* decoded_data) {
auto newValues{ _mm_add_ps(gather_values(up_sampled_vector), gather_values(decoded_data)) };
store_values(newValues, up_sampled_vector);
}
protected:
/**
* @brief Array for storing the values to be loaded/stored.
*/
alignas(16) float values[byte_blocks_per_register]{};
/**
* @brief Stores values from a 128-bit AVX vector to a storage location.
* @tparam value_type The target value type for storage.
* @param values_to_store The 128-bit AVX vector containing values to store.
* @param storage_location Pointer to the storage location.
*/
template<typename value_type> inline void store_values(const avx_float& values_to_store, value_type* storage_location) {
_mm_store_ps(values, values_to_store);
for (int64_t x = 0; x < byte_blocks_per_register; ++x) {
storage_location[x] = static_cast<value_type>(values[x]);
}
}
/**
* @brief Specialization for gathering non-float values into an AVX register.
* @tparam value_type The type of values being gathered.
* @tparam Indices Parameter pack of indices for gathering values.
* @return An AVX register containing gathered values.
*/
template<typename value_type> inline avx_float gather_values(value_type* values_new) {
for (uint64_t x = 0; x < byte_blocks_per_register; ++x) {
values[x] = static_cast<float>(values_new[x]);
}
return _mm_load_ps(values);
}
};
}
#endif

115
include/dpp/isa/avx2.h Normal file
View File

@ -0,0 +1,115 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#if defined _MSC_VER || defined __GNUC__ || defined __clang__
#include <immintrin.h>
#include <numeric>
#include <cstdint>
#include <limits>
namespace dpp {
using avx_2_float = __m256;
/**
* @brief A class for audio mixing operations using AVX2 instructions.
*/
class audio_mixer {
public:
/**
* @brief The number of 32-bit values per CPU register.
*/
inline static constexpr int32_t byte_blocks_per_register{ 8 };
/**
* @brief Collect a single register worth of data from data_in, apply gain and increment, and store the result in data_out.
* This version uses AVX2 instructions.
*
* @param data_in Pointer to the input array of int32_t values.
* @param data_out Pointer to the output array of int16_t values.
* @param current_gain The gain to be applied to the elements.
* @param increment The increment value to be added to each element.
*/
inline void collect_single_register(int32_t* data_in, int16_t* data_out, float current_gain, float increment) {
avx_2_float current_samples_new{ _mm256_mul_ps(gather_values(data_in),
_mm256_add_ps(_mm256_set1_ps(current_gain),
_mm256_mul_ps(_mm256_set1_ps(increment), _mm256_set_ps(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f)))) };
current_samples_new =
_mm256_blendv_ps(_mm256_max_ps(current_samples_new, _mm256_set1_ps(static_cast<float>(std::numeric_limits<int16_t>::min()))),
_mm256_min_ps(current_samples_new, _mm256_set1_ps(static_cast<float>(std::numeric_limits<int16_t>::max()))),
_mm256_cmp_ps(current_samples_new, _mm256_set1_ps(0.0f), _CMP_GE_OQ));
store_values(current_samples_new, data_out);
}
/**
* @brief Combine a register worth of elements from decoded_data and store the result in up_sampled_vector.
* This version uses AVX2 instructions.
*
* @param up_sampled_vector Pointer to the array of int32_t values.
* @param decoded_data Pointer to the array of int16_t values.
* @param x Index to select a specific set of elements to combine.
*/
inline void combine_samples(int32_t* up_sampled_vector, const int16_t* decoded_data) {
auto newValues{ _mm256_add_ps(gather_values(up_sampled_vector), gather_values(decoded_data)) };
store_values(newValues, up_sampled_vector);
}
protected:
/**
* @brief Array for storing the values to be loaded/stored.
*/
alignas(32) float values[byte_blocks_per_register]{};
/**
* @brief Stores values from a 256-bit AVX2 vector to a storage location.
* @tparam value_type The target value type for storage.
* @param values_to_store The 256-bit AVX2 vector containing values to store.
* @param storage_location Pointer to the storage location.
*/
template<typename value_type> inline void store_values(const avx_2_float& values_to_store, value_type* storage_location) {
_mm256_store_ps(values, values_to_store);
for (int64_t x = 0; x < byte_blocks_per_register; ++x) {
storage_location[x] = static_cast<value_type>(values[x]);
}
}
/**
* @brief Specialization for gathering non-float values into an AVX2 register.
* @tparam value_type The type of values being gathered.
* @tparam Indices Parameter pack of indices for gathering values.
* @return An AVX2 register containing gathered values.
*/
template<typename value_type> inline avx_2_float gather_values(value_type* values_new) {
for (uint64_t x = 0; x < byte_blocks_per_register; ++x) {
values[x] = static_cast<float>(values_new[x]);
}
return _mm256_load_ps(values);
}
};
}
#endif

118
include/dpp/isa/avx512.h Normal file
View File

@ -0,0 +1,118 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#if defined _MSC_VER || defined __GNUC__ || defined __clang__
#include <immintrin.h>
#include <numeric>
#include <cstdint>
#include <limits>
namespace dpp {
using avx_512_float = __m512;
/**
* @brief A class for audio mixing operations using AVX512 instructions.
*/
class audio_mixer {
public:
/**
* @brief The number of 32-bit values per CPU register.
*/
inline static constexpr int32_t byte_blocks_per_register{ 16 };
/**
* @brief Collect a single register worth of data from data_in, apply gain and increment, and store the result in data_out.
* This version uses AVX512 instructions.
*
* @param data_in Pointer to the input array of int32_t values.
* @param data_out Pointer to the output array of int16_t values.
* @param current_gain The gain to be applied to the elements.
* @param increment The increment value to be added to each element.
*/
inline void collect_single_register(int32_t* data_in, int16_t* data_out, float current_gain, float increment) {
avx_512_float current_samples_new{ _mm512_mul_ps(gather_values(data_in),
_mm512_add_ps(_mm512_set1_ps(current_gain),
_mm512_mul_ps(_mm512_set1_ps(increment),
_mm512_set_ps(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f)))) };
__m512 lower_limit = _mm512_set1_ps(static_cast<float>(std::numeric_limits<int16_t>::min()));
__m512 upper_limit = _mm512_set1_ps(static_cast<float>(std::numeric_limits<int16_t>::max()));
__mmask16 mask_ge = _mm512_cmp_ps_mask(current_samples_new, _mm512_set1_ps(0.0f), _CMP_GE_OQ);
current_samples_new = _mm512_mask_max_ps(current_samples_new, mask_ge, current_samples_new, lower_limit);
current_samples_new = _mm512_mask_min_ps(current_samples_new, ~mask_ge, current_samples_new, upper_limit);
store_values(current_samples_new, data_out);
}
/**
* @brief Combine a register worth of elements from decoded_data and store the result in up_sampled_vector.
* This version uses AVX512 instructions.
*
* @param up_sampled_vector Pointer to the array of int32_t values.
* @param decoded_data Pointer to the array of int16_t values.
*/
inline void combine_samples(int32_t* up_sampled_vector, const int16_t* decoded_data) {
auto newValues{ _mm512_add_ps(gather_values(up_sampled_vector), gather_values(decoded_data)) };
store_values(newValues, up_sampled_vector);
}
protected:
/**
* @brief Array for storing the values to be loaded/stored.
*/
alignas(64) float values[byte_blocks_per_register]{};
/**
* @brief Stores values from a 512-bit AVX512 vector to a storage location.
* @tparam value_type The target value type for storage.
* @param values_to_store The 512-bit AVX512 vector containing values to store.
* @param storage_location Pointer to the storage location.
*/
template<typename value_type> inline static void store_values(const avx_512_float& values_to_store, value_type* storage_location) {
_mm256_store_ps(values, values_to_store);
for (int64_t x = 0; x < byte_blocks_per_register; ++x) {
storage_location[x] = static_cast<value_type>(values[x]);
}
}
/**
* @brief Specialization for gathering non-float values into an AVX512 register.
* @tparam value_type The type of values being gathered.
* @tparam Indices Parameter pack of indices for gathering values.
* @return An AVX512 register containing gathered values.
*/
template<typename value_type> inline avx_512_float gather_values(value_type* values_new) {
for (uint64_t x = 0; x < byte_blocks_per_register; ++x) {
values[x] = static_cast<float>(values_new[x]);
}
return _mm512_load_ps(values);
}
};
}
#endif

View File

@ -0,0 +1,79 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <numeric>
#include <cstdint>
#include <limits>
namespace dpp {
/**
* @brief A class for audio mixing operations using x64 instructions.
*/
class audio_mixer {
public:
/*
* @brief The number of 32-bit values per CPU register.
*/
inline static constexpr int32_t byte_blocks_per_register{ 2 };
/**
* @brief Collect a single register worth of data from data_in, apply gain and increment, and store the result in data_out.
* This version uses x64 instructions.
*
* @param data_in Pointer to the input array of int32_t values.
* @param data_out Pointer to the output array of int16_t values.
* @param current_gain The gain to be applied to the elements.
* @param increment The increment value to be added to each element.
*/
inline static void collect_single_register(int32_t* data_in, int16_t* data_out, float current_gain, float increment) {
for (uint64_t x = 0; x < byte_blocks_per_register; ++x) {
auto increment_new = increment * x;
auto current_gain_new = current_gain + increment_new;
auto current_sample_new = data_in[x] * current_gain_new;
if (current_sample_new >= std::numeric_limits<int16_t>::max()) {
current_sample_new = std::numeric_limits<int16_t>::max();
}
else if (current_sample_new <= std::numeric_limits<int16_t>::min()) {
current_sample_new = std::numeric_limits<int16_t>::min();
}
data_out[x] = static_cast<int16_t>(current_sample_new);
}
}
/**
* @brief Combine a register worth of elements from decoded_data and store the result in up_sampled_vector.
* This version uses x64 instructions.
*
* @param up_sampled_vector Pointer to the array of int32_t values.
* @param decoded_data Pointer to the array of int16_t values.
*/
inline static void combine_samples(int32_t* up_sampled_vector, const int16_t* decoded_data) {
for (uint64_t x = 0; x < byte_blocks_per_register; ++x) {
up_sampled_vector[x] += static_cast<int32_t>(decoded_data[x]);
}
}
};
}

120
include/dpp/isa/neon.h Normal file
View File

@ -0,0 +1,120 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#if defined _MSC_VER || defined __GNUC__ || defined __clang__
#include <arm_neon.h>
#include <numeric>
#include <cstdint>
#include <limits>
namespace dpp {
using neon_float = float32x4_t;
/**
* @brief A class for audio mixing operations using ARM NEON instructions.
*/
class audio_mixer {
public:
/**
* @brief The number of 32-bit values per CPU register.
*/
inline static constexpr int32_t byte_blocks_per_register{ 4 };
/**
* @brief Collect a single register worth of data from data_in, apply gain and increment, and store the result in data_out.
* This version uses ARM NEON instructions.
*
* @param data_in Pointer to the input array of int32_t values.
* @param data_out Pointer to the output array of int16_t values.
* @param current_gain The gain to be applied to the elements.
* @param increment The increment value to be added to each element.
*/
inline void collect_single_register(int32_t* data_in, int16_t* data_out, float current_gain, float increment) {
neon_float gathered_values = gather_values(data_in);
neon_float gain_vector = vdupq_n_f32(current_gain);
static constexpr float data[4] = { 0.0f, 1.0f, 2.0f, 3.0f };
neon_float floats = vld1q_f32(data);
neon_float increment_vector = vmulq_f32(vdupq_n_f32(increment), floats);
neon_float current_samples_new = vmulq_f32(gathered_values, vaddq_f32(gain_vector, increment_vector));
// Clamping the values between int16_t min and max
neon_float min_val = vdupq_n_f32(static_cast<float>(std::numeric_limits<int16_t>::min()));
neon_float max_val = vdupq_n_f32(static_cast<float>(std::numeric_limits<int16_t>::max()));
current_samples_new = vmaxq_f32(current_samples_new, min_val);
current_samples_new = vminq_f32(current_samples_new, max_val);
store_values(current_samples_new, data_out);
}
/**
* @brief Combine a register worth of elements from decoded_data and store the result in up_sampled_vector.
* This version uses ARM NEON instructions.
*
* @param up_sampled_vector Pointer to the array of int32_t values.
* @param decoded_data Pointer to the array of int16_t values.
*/
inline void combine_samples(int32_t* up_sampled_vector, const int16_t* decoded_data) {
neon_float up_sampled = gather_values(up_sampled_vector);
neon_float decoded = gather_values(decoded_data);
neon_float newValues = vaddq_f32(up_sampled, decoded);
store_values(newValues, up_sampled_vector);
}
protected:
/**
* @brief Array for storing the values to be loaded/stored.
*/
alignas(16) float values[byte_blocks_per_register]{};
/**
* @brief Stores values from a 128-bit NEON vector to a storage location.
* @tparam value_type The target value type for storage.
* @param values_to_store The 128-bit NEON vector containing values to store.
* @param storage_location Pointer to the storage location.
*/
template<typename value_type> inline void store_values(const neon_float& values_to_store, value_type* storage_location) {
vst1q_f32(values, values_to_store);
for (int64_t x = 0; x < byte_blocks_per_register; ++x) {
storage_location[x] = static_cast<value_type>(values[x]);
}
}
/**
* @brief Specialization for gathering non-float values into a NEON register.
* @tparam value_type The type of values being gathered.
* @return A NEON register containing gathered values.
*/
template<typename value_type> inline neon_float gather_values(value_type* values_new) {
for (uint64_t x = 0; x < byte_blocks_per_register; ++x) {
values[x] = static_cast<float>(values_new[x]);
}
return vld1q_f32(values);
}
};
} // namespace dpp
#endif

View File

@ -0,0 +1,33 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#if AVX_TYPE == 1024
#include "isa/neon.h"
#elif AVX_TYPE == 512
#include "isa/avx512.h"
#elif AVX_TYPE == 2
#include "isa/avx2.h"
#elif AVX_TYPE == 1
#include "isa/avx.h"
#else
#include "isa/fallback.h"
#endif

32
include/dpp/json.h Normal file
View File

@ -0,0 +1,32 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#ifdef DPP_USE_EXTERNAL_JSON
#include <nlohmann/json.hpp>
#else
#include <dpp/nlohmann/json.hpp>
#endif
namespace dpp {
using json = nlohmann::json;
}

32
include/dpp/json_fwd.h Normal file
View File

@ -0,0 +1,32 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#ifdef DPP_USE_EXTERNAL_JSON
#include <nlohmann/json_fwd.hpp>
#else
#include <dpp/nlohmann/json_fwd.hpp>
#endif
namespace dpp {
using json = nlohmann::json;
}

View File

@ -0,0 +1,73 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/json.h>
namespace dpp {
/**
* @brief Represents an interface for an object that can optionally implement functions
* for converting to and from nlohmann::json. The methods are only present if the actual object
* also has those methods.
*
* @tparam T Type of class that implements the interface
*/
template<typename T>
struct json_interface {
/**
* @brief Convert object from nlohmann::json
*
* @param j nlohmann::json object
* @return T& Reference to self for fluent calling
*/
template <typename U = T, typename = decltype(std::declval<U&>().fill_from_json_impl(std::declval<nlohmann::json*>()))>
T& fill_from_json(nlohmann::json* j) {
return static_cast<T*>(this)->fill_from_json_impl(j);
}
/**
* @brief Convert object to nlohmann::json
*
* @param with_id Whether to include the ID or not
* @note Some fields are conditionally filled, do not rely on all fields being present
* @return json Json built from the structure
*/
template <typename U = T, typename = decltype(std::declval<U&>().to_json_impl(bool{}))>
auto to_json(bool with_id = false) const {
return static_cast<const T*>(this)->to_json_impl(with_id);
}
/**
* @brief Convert object to json string
*
* @param with_id Whether to include the ID or not
* @note Some fields are conditionally filled, do not rely on all fields being present
* @return std::string Json built from the structure
*/
template <typename U = T, typename = decltype(std::declval<U&>().to_json_impl(bool{}))>
std::string build_json(bool with_id = false) const {
return to_json(with_id).dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace);
}
};
}

116
include/dpp/managed.h Normal file
View File

@ -0,0 +1,116 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <string>
namespace dpp {
/** @brief The managed class is the base class for various types that can
* be stored in a cache that are identified by a dpp::snowflake id.
*/
class DPP_EXPORT managed {
public:
/**
* @brief Unique ID of object set by Discord.
* This value contains a timestamp, worker ID, internal server ID, and an incrementing value.
* Only the timestamp is relevant to us as useful metadata.
*/
snowflake id = {};
/**
* @brief Constructor, initialises id to 0.
*/
managed() = default;
/**
* @brief Constructor, initialises ID
* @param nid ID to set
*/
managed(const snowflake nid) : id{nid} {}
/**
* @brief Copy constructor
* @param rhs Object to copy
*/
managed(const managed &rhs) = default;
/**
* @brief Move constructor
*
* Effectively equivalent to copy constructor
* @param rhs Object to move from
*/
managed(managed &&rhs) = default;
/**
* @brief Destroy the managed object
*/
virtual ~managed() = default;
/**
* @brief Copy assignment operator
* @param rhs Object to copy
*/
managed &operator=(const managed& rhs) = default;
/**
* @brief Move assignment operator
* @param rhs Object to move from
*/
managed &operator=(managed&& rhs) = default;
/**
* @brief Get the creation time of this object according to Discord.
*
* @return double creation time inferred from the snowflake ID.
* The minimum possible value is the first second of 2015.
*/
constexpr double get_creation_time() const noexcept {
return id.get_creation_time();
};
/**
* @brief Comparison operator for comparing two managed objects by id
*
* @param other Other object to compare against
* @return true objects are the same id
* @return false objects are not the same id
*/
constexpr bool operator==(const managed& other) const noexcept {
return id == other.id;
}
/**
* @brief Comparison operator for comparing two managed objects by id
*
* @param other Other object to compare against
* @return true objects are not the same id
* @return false objects are the same id
*/
constexpr bool operator!=(const managed& other) const noexcept {
return id != other.id;
}
};
}

3016
include/dpp/message.h Normal file

File diff suppressed because it is too large Load Diff

89
include/dpp/misc-enum.h Normal file
View File

@ -0,0 +1,89 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <cstddef>
#include <cstdint>
namespace dpp {
/**
* @brief Supported image types for profile pictures and CDN endpoints
*/
enum image_type : uint8_t {
/**
* @brief image/png
*/
i_png,
/**
* @brief image/jpeg.
*/
i_jpg,
/**
* @brief image/gif.
*/
i_gif,
/**
* @brief Webp.
*/
i_webp,
};
/**
* @brief Log levels
*/
enum loglevel {
/**
* @brief Trace
*/
ll_trace = 0,
/**
* @brief Debug
*/
ll_debug,
/**
* @brief Information
*/
ll_info,
/**
* @brief Warning
*/
ll_warning,
/**
* @brief Error
*/
ll_error,
/**
* @brief Critical
*/
ll_critical
};
}

24596
include/dpp/nlohmann/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,175 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.2
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.2
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

46
include/dpp/once.h Normal file
View File

@ -0,0 +1,46 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <utility>
namespace dpp {
/**
* @brief Run some code within an if() statement only once.
*
* Use this template like this:
*
* ```
* if (dpp::run_once<struct any_unique_name_you_like_here>()) {
* // Your code here
* }
* ```
*
* @tparam T any unique 'tag' identifier name
* @return auto a true/false return to say if we should execute or not
*/
template <typename T> auto run_once() {
static auto called = false;
return !std::exchange(called, true);
};
}

459
include/dpp/permissions.h Normal file
View File

@ -0,0 +1,459 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/json.h>
#include <cstdint>
#include <type_traits>
namespace dpp {
/**
* @brief Represents the various discord permissions
*/
enum permissions : uint64_t {
/**
* @brief Allows creation of instant invites.
*/
p_create_instant_invite = 0x00000000001,
/**
* @brief Allows kicking members.
*/
p_kick_members = 0x00000000002,
/**
* @brief Allows banning members.
*/
p_ban_members = 0x00000000004,
/**
* @brief Allows all permissions and bypasses channel permission overwrites.
*/
p_administrator = 0x00000000008,
/**
* @brief Allows management and editing of channels.
*/
p_manage_channels = 0x00000000010,
/**
* @brief Allows management and editing of the guild.
*/
p_manage_guild = 0x00000000020,
/**
* @brief Allows for the addition of reactions to messages.
*/
p_add_reactions = 0x00000000040,
/**
* @brief Allows for viewing of audit logs.
*/
p_view_audit_log = 0x00000000080,
/**
* @brief Allows for using priority speaker in a voice channel.
*/
p_priority_speaker = 0x00000000100,
/**
* @brief Allows the user to go live.
*/
p_stream = 0x00000000200,
/**
* @brief Allows guild members to view a channel,
* which includes reading messages in text channels and joining voice channels.
*/
p_view_channel = 0x00000000400,
/**
* @brief Allows for sending messages in a channel.
*/
p_send_messages = 0x00000000800,
/**
* @brief Allows for sending of /tts messages.
*/
p_send_tts_messages = 0x00000001000,
/**
* @brief Allows for deletion of other users messages.
*/
p_manage_messages = 0x00000002000,
/**
* @brief Links sent by users with this permission will be auto-embedded.
*/
p_embed_links = 0x00000004000,
/**
* @brief Allows for uploading images and files.
*/
p_attach_files = 0x00000008000,
/**
* @brief Allows for reading of message history.
*/
p_read_message_history = 0x00000010000,
/**
* @brief Allows for using the everyone and the here tag to notify users in a channel.
*/
p_mention_everyone = 0x00000020000,
/**
* @brief Allows the usage of custom emojis from other servers.
*/
p_use_external_emojis = 0x00000040000,
/**
* @brief Allows for viewing guild insights.
*/
p_view_guild_insights = 0x00000080000,
/**
* @brief Allows for joining of a voice channel.
*/
p_connect = 0x00000100000,
/**
* @brief Allows for speaking in a voice channel.
*/
p_speak = 0x00000200000,
/**
* @brief Allows for muting members in a voice channel.
*/
p_mute_members = 0x00000400000,
/**
* @brief Allows for deafening of members in a voice channel.
*/
p_deafen_members = 0x00000800000,
/**
* @brief Allows for moving of members between voice channels.
*/
p_move_members = 0x00001000000,
/**
* @brief Allows for using voice-activity-detection in a voice channel.
*/
p_use_vad = 0x00002000000,
/**
* @brief Allows for modification of own nickname.
*/
p_change_nickname = 0x00004000000,
/**
* @brief Allows for modification of other users nicknames.
*/
p_manage_nicknames = 0x00008000000,
/**
* @brief Allows management and editing of roles.
*/
p_manage_roles = 0x00010000000,
/**
* @brief Allows management and editing of webhooks.
*/
p_manage_webhooks = 0x00020000000,
/**
* @brief Allows management and editing of emojis and stickers.
*/
p_manage_emojis_and_stickers = 0x00040000000,
/**
* @brief Allows members to use application commands,
* including slash commands and context menus.
*/
p_use_application_commands = 0x00080000000,
/**
* @brief Allows for requesting to speak in stage channels.
*
* @warning Discord: This permission is under active development and may be changed or removed.
*/
p_request_to_speak = 0x00100000000,
/**
* @brief Allows for management (creation, updating, deleting, starting) of scheduled events.
*/
p_manage_events = 0x00200000000,
/**
* @brief Allows for deleting and archiving threads, and viewing all private threads.
*/
p_manage_threads = 0x00400000000,
/**
* @brief Allows for creating public and announcement threads.
*/
p_create_public_threads = 0x00800000000,
/**
* @brief Allows for creating private threads.
*/
p_create_private_threads = 0x01000000000,
/**
* @brief Allows the usage of custom stickers from other servers.
*/
p_use_external_stickers = 0x02000000000,
/**
* @brief Allows for sending messages in threads.
*/
p_send_messages_in_threads = 0x04000000000,
/**
* @brief Allows for using activities (applications with the EMBEDDED flag) in a voice channel.
*/
p_use_embedded_activities = 0x08000000000,
/**
* @brief Allows for timing out users
* to prevent them from sending or reacting to messages in chat and threads,
* and from speaking in voice and stage channels.
*/
p_moderate_members = 0x10000000000,
/**
* @brief Allows for viewing role subscription insights.
*/
p_view_creator_monetization_analytics = 0x20000000000,
/**
* @brief Allows for using soundboard in a voice channel.
*/
p_use_soundboard = 0x40000000000,
/**
* @brief Allows the usage of custom soundboard sounds from other servers.
*/
p_use_external_sounds = 0x0000200000000000,
/**
* @brief Allows sending voice messages.
*/
p_send_voice_messages = 0x0000400000000000,
/**
* @brief Allows use of Clyde AI.
*/
p_use_clyde_ai = 0x0000800000000000,
};
/**
* @brief Represents the various discord permissions
* @deprecated Use dpp::permissions instead.
*/
using role_permissions = permissions;
/**
* @brief Represents a permission bitmask (refer to enum dpp::permissions) which are held in an uint64_t
*/
class DPP_EXPORT permission {
protected:
/**
* @brief The permission bitmask value
*/
uint64_t value{0};
public:
/**
* @brief Default constructor, initializes permission to 0
*/
constexpr permission() = default;
/**
* @brief Bitmask constructor, initializes permission to the argument
* @param value The bitmask to initialize the permission to
*/
constexpr permission(uint64_t value) noexcept : value{value} {}
/**
* @brief For acting like an integer
* @return The permission bitmask value
*/
constexpr operator uint64_t() const noexcept {
return value;
}
/**
* @brief For acting like an integer
* @return A reference to the permission bitmask value
*/
constexpr operator uint64_t &() noexcept {
return value;
}
/**
* @brief For building json
* @return The permission bitmask value as a string
*/
operator nlohmann::json() const;
/**
* @brief Check for certain permissions, taking into account administrator privileges. It uses the Bitwise AND operator
* @tparam T one or more uint64_t permission bits
* @param values The permissions (from dpp::permissions) to check for
*
* **Example:**
*
* ```cpp
* bool is_mod = permission.can(dpp::p_kick_members, dpp::p_ban_members);
* // Returns true if it has permission to p_kick_members and p_ban_members
* ```
*
* @return bool True if it has **all** the given permissions or dpp::p_administrator
*/
template <typename... T>
constexpr bool can(T... values) const noexcept {
return has(values...) || (value & p_administrator);
}
/**
* @brief Check for certain permissions, taking into account administrator privileges. It uses the Bitwise AND operator
* @tparam T one or more uint64_t permission bits
* @param values The permissions (from dpp::permissions) to check for
*
* **Example:**
*
* ```cpp
* bool is_mod = permission.can_any(dpp::p_kick_members, dpp::p_ban_members);
* // Returns true if it has permission to p_kick_members or p_ban_members
* ```
*
* @return bool True if it has **any** of the given permissions or dpp::p_administrator
*/
template <typename... T>
constexpr bool can_any(T... values) const noexcept {
return has_any(values...) || (value & p_administrator);
}
/**
* @brief Check for permission flags set. It uses the Bitwise AND operator
* @tparam T one or more uint64_t permission bits
* @param values The permissions (from dpp::permissions) to check for
*
* **Example:**
*
* ```cpp
* bool is_mod = permission.has(dpp::p_kick_members, dpp::p_ban_members);
* // Returns true if the permission bitmask contains p_kick_members and p_ban_members
* ```
*
* @return bool True if it has **all** the given permissions
*/
template <typename... T>
constexpr bool has(T... values) const noexcept {
return (value & (0 | ... | values)) == (0 | ... | values);
}
/**
* @brief Check for permission flags set. It uses the Bitwise AND operator
* @tparam T one or more uint64_t permission bits
* @param values The permissions (from dpp::permissions) to check for
*
* **Example:**
*
* ```cpp
* bool is_mod = permission.has_any(dpp::p_administrator, dpp::p_ban_members);
* // Returns true if the permission bitmask contains p_administrator or p_ban_members
* ```
*
* @return bool True if it has **any** of the given permissions
*/
template <typename... T>
constexpr bool has_any(T... values) const noexcept {
return (value & (0 | ... | values)) != 0;
}
/**
* @brief Add a permission with the Bitwise OR operation
* @tparam T one or more uint64_t permission bits
* @param values The permissions (from dpp::permissions) to add
*
* **Example:**
*
* ```cpp
* permission.add(dpp::p_view_channel, dpp::p_send_messages);
* // Adds p_view_channel and p_send_messages to the permission bitmask
* ```
*
* @return permission& reference to self for chaining
*/
template <typename... T>
std::enable_if_t<(std::is_convertible_v<T, uint64_t> && ...), permission&>
constexpr add(T... values) noexcept {
value |= (0 | ... | values);
return *this;
}
/**
* @brief Assign permissions. This will reset the bitmask to the new value.
* @tparam T one or more uint64_t permission bits
* @param values The permissions (from dpp::permissions) to set
*
* **Example:**
*
* ```cpp
* permission.set(dpp::p_view_channel, dpp::p_send_messages);
* ```
*
* @return permission& reference to self for chaining
*/
template <typename... T>
std::enable_if_t<(std::is_convertible_v<T, uint64_t> && ...), permission&>
constexpr set(T... values) noexcept {
value = (0 | ... | values);
return *this;
}
/**
* @brief Remove a permission with the Bitwise NOT operation
* @tparam T one or more uint64_t permission bits
* @param values The permissions (from dpp::permissions) to remove
*
* **Example:**
*
* ```cpp
* permission.remove(dpp::p_view_channel, dpp::p_send_messages);
* // Removes p_view_channel and p_send_messages permission
* ```
*
* @return permission& reference to self for chaining
*/
template <typename... T>
std::enable_if_t<(std::is_convertible_v<T, uint64_t> && ...), permission&>
constexpr remove(T... values) noexcept {
value &= ~(0 | ... | values);
return *this;
}
};
}

598
include/dpp/presence.h Normal file
View File

@ -0,0 +1,598 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/emoji.h>
#include <dpp/json_fwd.h>
#include <unordered_map>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Presence flags bitmask
*/
enum presence_flags {
/**
* @brief Desktop: Online.
*/
p_desktop_online = 0b00000001,
/**
* @brief Desktop: DND.
*/
p_desktop_dnd = 0b00000010,
/**
* @brief Desktop: Idle.
*/
p_desktop_idle = 0b00000011,
/**
* @brief Web: Online.
*/
p_web_online = 0b00000100,
/**
* @brief Web: DND.
*/
p_web_dnd = 0b00001000,
/**
* @brief Web: Idle.
*/
p_web_idle = 0b00001100,
/**
* @brief Mobile: Online.
*/
p_mobile_online = 0b00010000,
/**
* @brief Mobile: DND.
*/
p_mobile_dnd = 0b00100000,
/**
* @brief Mobile: Idle.
*/
p_mobile_idle = 0b00110000,
/**
* @brief General: Online.
*/
p_status_online = 0b01000000,
/**
* @brief General: DND.
*/
p_status_dnd = 0b10000000,
/**
* @brief General: Idle.
*/
p_status_idle = 0b11000000
};
/**
* @brief Online presence status values
*/
enum presence_status : uint8_t {
/**
* @brief Offline.
*/
ps_offline = 0,
/**
* @brief Online.
*/
ps_online = 1,
/**
* @brief DND.
*/
ps_dnd = 2,
/**
* @brief Idle.
*/
ps_idle = 3,
/**
* @brief Invisible (show as offline).
*/
ps_invisible = 4,
};
/**
* @brief Bit shift for desktop status.
*/
#define PF_SHIFT_DESKTOP 0
/**
* @brief Bit shift for web status.
*/
#define PF_SHIFT_WEB 2
/**
* @brief Bit shift for mobile status.
*/
#define PF_SHIFT_MOBILE 4
/**
* @brief Bit shift for main status.
*/
#define PF_SHIFT_MAIN 6
/**
* @brief Bit mask for status.
*/
#define PF_STATUS_MASK 0b00000011
/**
* @brief Bit mask for clearing desktop status.
*/
#define PF_CLEAR_DESKTOP 0b11111100
/**
* @brief Bit mask for clearing web status.
*/
#define PF_CLEAR_WEB 0b11110011
/**
* @brief Bit mask for clearing mobile status.
*/
#define PF_CLEAR_MOBILE 0b11001111
/**
* @brief Bit mask for clearing main status.
*/
#define PF_CLEAR_STATUS 0b00111111
/**
* @brief Game types
*/
enum activity_type : uint8_t {
/**
* @brief "Playing ..."
*/
at_game = 0,
/**
* @brief "Streaming ..."
*/
at_streaming = 1,
/**
* @brief "Listening to..."
*/
at_listening = 2,
/**
* @brief "Watching..."
*/
at_watching = 3,
/**
* @brief "Emoji..."
*/
at_custom = 4,
/**
* @brief "Competing in..."
*/
at_competing = 5
};
/**
* @brief Activity types for rich presence
*/
enum activity_flags {
/**
* @brief In an instance.
*/
af_instance = 0b000000001,
/**
* @brief Joining.
*/
af_join = 0b000000010,
/**
* @brief Spectating.
*/
af_spectate = 0b000000100,
/**
* @brief Sending join request.
*/
af_join_request = 0b000001000,
/**
* @brief Synchronising.
*/
af_sync = 0b000010000,
/**
* @brief Playing.
*/
af_play = 0b000100000,
/**
* @brief Party privacy friends.
*/
af_party_privacy_friends = 0b001000000,
/**
* @brief Party privacy voice channel.
*/
af_party_privacy_voice_channel = 0b010000000,
/**
* @brief Embedded.
*/
af_embedded = 0b100000000
};
/**
* @brief An activity button is a custom button shown in the rich presence. Can be to join a game or whatever
*/
struct DPP_EXPORT activity_button {
public:
/**
* @brief The text shown on the button (1-32 characters).
*/
std::string label;
/**
* @brief The url opened when clicking the button (1-512 characters, can be empty).
*
* @note Bots cannot access the activity button URLs.
*/
std::string url;
/** Constructor */
activity_button() = default;
};
/**
* @brief An activity asset are the images and the hover text displayed in the rich presence
*/
struct DPP_EXPORT activity_assets {
public:
/**
* @brief The large asset image which usually contain snowflake ID or prefixed image ID.
*/
std::string large_image;
/**
* @brief Text displayed when hovering over the large image of the activity.
*/
std::string large_text;
/**
* @brief The small asset image which usually contain snowflake ID or prefixed image ID.
*/
std::string small_image;
/**
* @brief Text displayed when hovering over the small image of the activity.
*/
std::string small_text;
/** Constructor */
activity_assets() = default;
};
/**
* @brief Secrets for Rich Presence joining and spectating.
*/
struct DPP_EXPORT activity_secrets {
public:
/**
* @brief The secret for joining a party.
*/
std::string join;
/**
* @brief The secret for spectating a game.
*/
std::string spectate;
/**
* @brief The secret for a specific instanced match.
*/
std::string match;
/** Constructor */
activity_secrets() = default;
};
/**
* @brief Information for the current party of the player
*/
struct DPP_EXPORT activity_party {
public:
/**
* @brief The ID of the party.
*/
snowflake id;
/**
* @brief The party's current size.
* Used to show the party's current size.
*/
int32_t current_size;
/**
* @brief The party's maximum size.
* Used to show the party's maximum size.
*/
int32_t maximum_size;
/** Constructor */
activity_party();
};
/**
* @brief An activity is a representation of what a user is doing. It might be a game, or a website, or a movie. Whatever.
*/
class DPP_EXPORT activity {
public:
/**
* @brief Name of activity.
* e.g. "Fortnite", "Mr Boom's Firework Factory", etc.
*/
std::string name;
/**
* @brief State of activity or the custom user status.
* e.g. "Waiting in lobby".
*/
std::string state;
/**
* @brief What the player is currently doing.
*/
std::string details;
/**
* @brief Images for the presence and their hover texts.
*/
activity_assets assets;
/**
* @brief URL of activity (this is also named details).
*
* @note Only applicable for certain sites such a YouTube
*/
std::string url;
/**
* @brief The custom buttons shown in the Rich Presence (max 2).
*/
std::vector<activity_button> buttons;
/**
* @brief The emoji used for the custom status.
*/
dpp::emoji emoji;
/**
* @brief Information of the current party if there is one.
*/
activity_party party;
/**
* @brief Secrets for rich presence joining and spectating.
*/
activity_secrets secrets;
/**
* @brief Activity type.
*/
activity_type type;
/**
* @brief Time activity was created.
*/
time_t created_at;
/**
* @brief Start time.
* e.g. when game was started.
*/
time_t start;
/**
* @brief End time.
* e.g. for songs on spotify.
*/
time_t end;
/**
* @brief Creating application.
* e.g. a linked account on the user's client.
*/
snowflake application_id;
/**
* @brief Flags bitmask from dpp::activity_flags.
*/
uint8_t flags;
/**
* @brief Whether or not the activity is an instanced game session.
*/
bool is_instance;
/**
* @brief Get the assets large image url if they have one, otherwise returns an empty string. In case of prefixed image IDs (mp:{image_id}) it returns an empty string.
*
* @see https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-asset-image
*
* @param size The size of the image in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized image is returned.
* @param format The format to use for the avatar. It can be one of `i_webp`, `i_jpg` or `i_png`.
* @return std::string image url or an empty string, if required attributes are missing or an invalid format was passed
*/
std::string get_large_asset_url(uint16_t size = 0, const image_type format = i_png) const;
/**
* @brief Get the assets small image url if they have one, otherwise returns an empty string. In case of prefixed image IDs (mp:{image_id}) it returns an empty string.
*
* @see https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-asset-image
*
* @param size The size of the image in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized image is returned.
* @param format The format to use for the avatar. It can be one of `i_webp`, `i_jpg` or `i_png`.
* @return std::string image url or an empty string, if required attributes are missing or an invalid format was passed
*/
std::string get_small_asset_url(uint16_t size = 0, const image_type format = i_png) const;
activity();
/**
* @brief Construct a new activity
*
* @param typ activity type
* @param nam Name of the activity
* @param stat State of the activity
* @param url_ url of the activity, only works for certain sites, such as YouTube
*/
activity(const activity_type typ, const std::string& nam, const std::string& stat, const std::string& url_);
};
/**
* @brief Represents user presence, e.g. what game they are playing and if they are online
*/
class DPP_EXPORT presence : public json_interface<presence> {
protected:
friend struct json_interface<presence>;
/** Fill this object from json.
* @param j JSON object to fill from
* @return A reference to self
*/
presence& fill_from_json_impl(nlohmann::json* j);
/** Build JSON from this object.
*
* @note This excludes any part of the presence object that are not valid for websockets and bots,
* and includes websocket opcode 3. You will not get what you expect if you call this on a user's
* presence received from on_presence_update or on_guild_create!
*
* @param with_id Add ID to output
* @return The JSON text of the presence
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief The user the presence applies to.
*/
snowflake user_id;
/**
* @brief Guild ID.
*
* @note Apparently, Discord supports this internally, but the client doesn't...
*/
snowflake guild_id;
/**
* @brief Flags bitmask containing dpp::presence_flags
*/
uint8_t flags;
/**
* @brief List of activities.
*/
std::vector<activity> activities;
/** Constructor */
presence();
/**
* @brief Construct a new presence object with some parameters for sending to a websocket
*
* @param status Status of the activity
* @param type Type of activity
* @param activity_description Description of the activity
*/
presence(presence_status status, activity_type type, const std::string& activity_description);
/**
* @brief Construct a new presence object with some parameters for sending to a websocket.
*
* @param status Status of the activity
* @param a Activity itself
*/
presence(presence_status status, const activity& a);
/** Destructor */
~presence();
/**
* @brief The users status on desktop
* @return The user's status on desktop
*/
presence_status desktop_status() const;
/**
* @brief The user's status on web
* @return The user's status on web
*/
presence_status web_status() const;
/**
* @brief The user's status on mobile
* @return The user's status on mobile
*/
presence_status mobile_status() const;
/**
* @brief The user's status as shown to other users
* @return The user's status as shown to other users
*/
presence_status status() const;
/**
* @brief Build JSON from this object.
*
* @note This excludes any part of the presence object that are not valid for websockets and bots,
* and includes websocket opcode 3. You will not get what you expect if you call this on a user's
* presence received from on_presence_update or on_guild_create!
*
* @param with_id Add ID to output
* @return The JSON of the presence
*/
json to_json(bool with_id = false) const; // Intentional shadow of json_interface, mostly present for documentation
};
/**
* @brief A container of presences
*/
typedef std::unordered_map<snowflake, presence> presence_map;
}

80
include/dpp/prune.h Normal file
View File

@ -0,0 +1,80 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Defines a request to count prunable users, or start a prune operation
*/
struct DPP_EXPORT prune : public json_interface<prune> {
protected:
friend struct json_interface<prune>;
/** Fill this object from json.
* @param j JSON object to fill from
* @return A reference to self
*/
prune& fill_from_json_impl(nlohmann::json* j);
/** Build JSON from this object.
* @param with_prune_count True if the prune count boolean is to be set in the built JSON
* @return The JSON of the prune object
*/
virtual json to_json_impl(bool with_prune_count = false) const;
public:
/**
* @brief Destroy this prune object
*/
virtual ~prune() = default;
/**
* @brief Number of days to include in the prune.
*/
uint32_t days = 0;
/**
* @brief Roles to include in the prune (empty to include everyone).
*/
std::vector<snowflake> include_roles;
/**
* @brief True if the count of pruneable users should be returned.
* @warning Discord recommend not using this on big guilds.
*/
bool compute_prune_count;
/**
* @brief Build JSON from this object.
*
* @param with_id True if the prune count boolean is to be set in the built JSON
* @return The JSON of the prune object
*/
json to_json(bool with_id = false) const; // Intentional shadow of json_interface, mostly present for documentation
};
}

639
include/dpp/queues.h Normal file
View File

@ -0,0 +1,639 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <unordered_map>
#include <string>
#include <queue>
#include <map>
#include <shared_mutex>
#include <memory>
#include <vector>
#include <functional>
#include <atomic>
#include <dpp/httpsclient.h>
namespace dpp {
/**
* @brief Error values. Most of these are currently unused in https_client.
*/
enum http_error : uint8_t {
/**
* @brief Request successful.
*/
h_success = 0,
/**
* @brief Status unknown.
*/
h_unknown,
/**
* @brief Connect failed.
*/
h_connection,
/**
* @brief Invalid local ip address.
*/
h_bind_ip_address,
/**
* @brief Read error.
*/
h_read,
/**
* @brief Write error.
*/
h_write,
/**
* @brief Too many 30x redirects.
*/
h_exceed_redirect_count,
/**
* @brief Request cancelled.
*/
h_canceled,
/**
* @brief SSL connection error.
*/
h_ssl_connection,
/**
* @brief SSL cert loading error.
*/
h_ssl_loading_certs,
/**
* @brief SSL server verification error.
*/
h_ssl_server_verification,
/**
* @brief Unsupported multipart boundary characters.
*/
h_unsupported_multipart_boundary_chars,
/**
* @brief Compression error.
*/
h_compression,
};
/**
* @brief The result of any HTTP request. Contains the headers, vital
* rate limit figures, and returned request body.
*/
struct DPP_EXPORT http_request_completion_t {
/**
* @brief HTTP headers of response.
*/
std::multimap<std::string, std::string> headers;
/**
* @brief HTTP status.
* e.g. 200 = OK, 404 = Not found, 429 = Rate limited, etc.
*/
uint16_t status = 0;
/**
* @brief Error status.
* e.g. if the request could not connect at all.
*/
http_error error = h_success;
/**
* @brief Ratelimit bucket.
*/
std::string ratelimit_bucket;
/**
* @brief Ratelimit limit of requests.
*/
uint64_t ratelimit_limit = 0;
/**
* @brief Ratelimit remaining requests.
*/
uint64_t ratelimit_remaining = 0;
/**
* @brief Ratelimit reset after (seconds).
*/
uint64_t ratelimit_reset_after = 0;
/**
* @brief Ratelimit retry after (seconds).
*/
uint64_t ratelimit_retry_after = 0;
/**
* @brief True if this request has caused us to be globally rate limited.
*/
bool ratelimit_global = false;
/**
* @brief Reply body.
*/
std::string body;
/**
* @brief Ping latency.
*/
double latency;
};
/**
* @brief Results of HTTP requests are called back to these std::function types.
*
* @note Returned http_completion_events are called ASYNCHRONOUSLY in your
* code which means they execute in a separate thread, results for the requests going
* into a dpp::thread_pool. Completion events may not arrive in order depending on if
* one request takes longer than another. Using the callbacks or using coroutines
* correctly ensures that the order they arrive in the queue does not negatively affect
* your code.
*/
typedef std::function<void(const http_request_completion_t&)> http_completion_event;
/**
* @brief Various types of http method supported by the Discord API
*/
enum http_method {
/**
* @brief GET.
*/
m_get,
/**
* @brief POST.
*/
m_post,
/**
* @brief PUT.
*/
m_put,
/**
* @brief PATCH.
*/
m_patch,
/**
* @brief DELETE.
*/
m_delete
};
/**
* @brief A HTTP request.
*
* You should instantiate one of these objects via its constructor,
* and pass a pointer to it into an instance of request_queue. Although you can
* directly call the run() method of the object and it will make a HTTP call, be
* aware that if you do this, it will be a **BLOCKING call** (not asynchronous) and
* will not respect rate limits, as both of these functions are managed by the
* request_queue class.
*/
class DPP_EXPORT http_request {
/**
* @brief Completion callback.
*/
http_completion_event complete_handler;
/**
* @brief True if request has been made.
*/
bool completed;
/**
* @brief True for requests that are not going to discord (rate limits code skipped).
*/
bool non_discord;
/**
* @brief HTTPS client
*/
std::unique_ptr<https_client> cli;
public:
/**
* @brief Endpoint name
* e.g. /api/users.
*/
std::string endpoint;
/**
* @brief Major and minor parameters.
*/
std::string parameters;
/**
* @brief Postdata for POST and PUT.
*/
std::string postdata;
/**
* @brief HTTP method for request.
*/
http_method method;
/**
* @brief Audit log reason for Discord requests, if non-empty.
*/
std::string reason;
/**
* @brief Upload file name (server side).
*/
std::vector<std::string> file_name;
/**
* @brief Upload file contents (binary).
*/
std::vector<std::string> file_content;
/**
* @brief Upload file mime types.
* application/octet-stream if unspecified.
*/
std::vector<std::string> file_mimetypes;
/**
* @brief Request mime type.
*/
std::string mimetype;
/**
* @brief Request headers (non-discord requests only).
*/
std::multimap<std::string, std::string> req_headers;
/**
* @brief Waiting for rate limit to expire.
*/
bool waiting;
/**
* @brief HTTP protocol.
*/
std::string protocol;
/**
* @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request().
* @param _endpoint The API endpoint, e.g. /api/guilds
* @param _parameters Major and minor parameters for the endpoint e.g. a user id or guild id
* @param completion completion event to call when done
* @param _postdata Data to send in POST and PUT requests
* @param method The HTTP method to use from dpp::http_method
* @param audit_reason Audit log reason to send, empty to send none
* @param filename The filename (server side) of any uploaded file
* @param filecontent The binary content of any uploaded file for the request
* @param filemimetype The MIME type of any uploaded file for the request
* @param http_protocol HTTP protocol
*/
http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata = "", http_method method = m_get, const std::string &audit_reason = "", const std::string &filename = "", const std::string &filecontent = "", const std::string &filemimetype = "", const std::string &http_protocol = "1.1");
/**
* @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request().
* @param _endpoint The API endpoint, e.g. /api/guilds
* @param _parameters Major and minor parameters for the endpoint e.g. a user id or guild id
* @param completion completion event to call when done
* @param _postdata Data to send in POST and PUT requests
* @param method The HTTP method to use from dpp::http_method
* @param audit_reason Audit log reason to send, empty to send none
* @param filename The filename (server side) of any uploaded file
* @param filecontent The binary content of any uploaded file for the request
* @param filemimetypes The MIME type of any uploaded file for the request
* @param http_protocol HTTP protocol
*/
http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata = "", http_method method = m_get, const std::string &audit_reason = "", const std::vector<std::string> &filename = {}, const std::vector<std::string> &filecontent = {}, const std::vector<std::string> &filemimetypes = {}, const std::string &http_protocol = "1.1");
/**
* @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request().
* @param _url Raw HTTP url
* @param completion completion event to call when done
* @param method The HTTP method to use from dpp::http_method
* @param _postdata Data to send in POST and PUT requests
* @param _mimetype POST data mime type
* @param _headers HTTP headers to send
* @param http_protocol HTTP protocol
*/
http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap<std::string, std::string> &_headers = {}, const std::string &http_protocol = "1.1");
/**
* @brief Destroy the http request object
*/
~http_request();
/**
* @brief Call the completion callback, if the request is complete.
* @param c callback to call
*/
void complete(const http_request_completion_t &c);
/**
* @brief Execute the HTTP request and mark the request complete.
* @param processor Request concurrency queue this request was created by
* @param owner creating cluster
*/
http_request_completion_t run(class request_concurrency_queue* processor, class cluster* owner);
/**
* @brief Returns true if the request is complete
* @return True if completed
* */
bool is_completed();
/**
* @brief Get the HTTPS client used to perform this request, or nullptr if there is none
* @return https_client object
*/
https_client* get_client() const;
};
/**
* @brief A rate limit bucket. The library builds one of these for
* each endpoint.
*/
struct DPP_EXPORT bucket_t {
/**
* @brief Request limit.
*/
uint64_t limit;
/**
* @brief Requests remaining.
*/
uint64_t remaining;
/**
* @brief Rate-limit of this bucket resets after this many seconds.
*/
uint64_t reset_after;
/**
* @brief Rate-limit of this bucket can be retried after this many seconds.
*/
uint64_t retry_after;
/**
* @brief Timestamp this buckets counters were updated.
*/
time_t timestamp;
};
/**
* @brief Represents a timer instance in a pool handling requests to HTTP(S) servers.
* There are several of these, the total defined by a constant in cluster.cpp, and each
* one will always receive requests for the same rate limit bucket based on its endpoint
* portion of the url. This makes rate limit handling reliable and easy to manage.
* Each of these also has its own mutex, making it thread safe to call and use these
* from anywhere in the code.
*/
class DPP_EXPORT request_concurrency_queue {
public:
/**
* @brief Queue index
*/
int in_index{0};
/**
* @brief True if ending.
*/
std::atomic<bool> terminating;
/**
* @brief Request queue that owns this request_concurrency_queue.
*/
class request_queue* requests;
/**
* @brief The cluster that owns this request_concurrency_queue.
*/
class cluster* creator;
/**
* @brief Inbound queue mutex thread safety.
*/
std::shared_mutex in_mutex;
/**
* @brief Inbound queue timer. The timer is called every second,
* and when it wakes up it checks for requests pending to be sent in the queue.
* If there are any requests and we are not waiting on rate limit, it will send them,
* else it will wait for the rate limit to expire.
*/
dpp::timer in_timer;
/**
* @brief Rate-limit bucket counters.
*/
std::map<std::string, bucket_t> buckets;
/**
* @brief Queue of requests to be made. Sorted by http_request::endpoint.
*/
std::vector<std::unique_ptr<http_request>> requests_in;
/**
* @brief Requests to remove after a set amount of time has passed
*/
std::vector<std::unique_ptr<http_request>> removals;
/**
* @brief Timer callback
* @param index Index ID for this timer
*/
void tick_and_deliver_requests(uint32_t index);
/**
* @brief Construct a new concurrency queue object
*
* @param owner Owning cluster
* @param req_q Owning request queue
* @param index Queue index number, uniquely identifies this queue for hashing
*/
request_concurrency_queue(class cluster* owner, class request_queue* req_q, uint32_t index);
/**
* @brief Destroy the concurrency queue object
* This will stop the timer.
*/
~request_concurrency_queue();
/**
* @brief Flags the queue as terminating
* This will set the internal atomic bool that indicates this queue is to accept no more requests
*/
void terminate();
/**
* @brief Post a http_request to this queue.
*
* @param req http_request to post. The pointer will be freed when it has
* been executed.
*/
void post_request(std::unique_ptr<http_request> req);
};
/**
* @brief The request_queue class manages rate limits and marshalls HTTP requests that have
* been built as http_request objects.
*
* It ensures asynchronous delivery of events and queueing of requests.
*
* It will spawn multiple timers to make outbound HTTP requests and then call the callbacks
* of those requests on completion within the dpp::thread_pool for the cluster.
* If the user decides to take a long time processing a reply in their callback it won't affect
* when other requests are sent, and if a HTTP request takes a long time due to latency, it won't
* hold up user processing.
*
* There are usually two request_queue objects in each dpp::cluster, one of which is used
* internally for the various REST methods to Discord such as sending messages, and the other
* used to support user REST calls via dpp::cluster::request(). They are separated so that the
* one for user requests can be specifically configured to never ever send the Discord token
* unless it is explicitly placed into the request, for security reasons.
*/
class DPP_EXPORT request_queue {
public:
/**
* @brief Required so request_concurrency_queue can access these member variables
*/
friend class request_concurrency_queue;
/**
* @brief The cluster that owns this request_queue
*/
class cluster* creator;
/**
* @brief A completed request. Contains both the request and the response
*/
struct completed_request {
/**
* @brief Request sent
*/
std::unique_ptr<http_request> request;
/**
* @brief Response to the request
*/
std::unique_ptr<http_request_completion_t> response;
};
/**
* @brief A vector of timers forming a pool.
*
* There are a set number of these defined by a constant in cluster.cpp. A request is always placed
* on the same element in this vector, based upon its url, so that two conditions are satisfied:
*
* 1) Any requests for the same ratelimit bucket are handled by the same concurrency queue in the pool so that
* they do not create unnecessary 429 errors,
* 2) Requests for different endpoints go into different buckets, so that they may be requested in parallel
* A global ratelimit event pauses all timers in the pool. These are few and far between.
*/
std::vector<std::unique_ptr<request_concurrency_queue>> requests_in;
/**
* @brief Set to true if the timers should terminate.
* When this is set to true no further requests are accepted to the queues.
*/
std::atomic<bool> terminating;
/**
* @brief True if globally rate limited
*
* When globally rate limited the concurrency queues associated with this request queue
* will not process any requests in their timers until the global rate limit expires.
*/
bool globally_ratelimited;
/**
* @brief When we are globally rate limited until (unix epoch)
*
* @note Only valid if globally_rate limited is true. If we are globally rate limited,
* queues in this class will not process requests until the current unix epoch time
* is greater than this time.
*/
time_t globally_limited_until;
/**
* @brief Number of request queues in the pool. This is the direct size of the requests_in
* vector.
*/
uint32_t in_queue_pool_size;
/**
* @brief constructor
* @param owner The creating cluster.
* @param request_concurrency The number of http request queues to allocate.
* Each request queue is a dpp::timer which ticks every second looking for new
* requests to run. The timer will hold back requests if we are waiting as to comply
* with rate limits. Adding a request to this class will cause the queue it is placed in
* to run immediately but this cannot override rate limits.
* By default eight concurrency queues are allocated.
* Side effects: Creates timers for the queue
*/
request_queue(class cluster* owner, uint32_t request_concurrency = 8);
/**
* @brief Get the request queue concurrency count
* @return uint32_t number of request queues that are active
*/
uint32_t get_request_queue_count() const;
/**
* @brief Destroy the request queue object.
* Side effects: Ends and deletes concurrency timers
*/
~request_queue();
/**
* @brief Put a http_request into the request queue.
* @note Will use a simple hash function to determine which of the 'in queues' to place
* this request onto.
* @param req request to add
* @return reference to self
*/
request_queue& post_request(std::unique_ptr<http_request> req);
/**
* @brief Returns true if the bot is currently globally rate limited
* @return true if globally rate limited
*/
bool is_globally_ratelimited() const;
/**
* @brief Returns the number of active requests on this queue
* @return Total number of active requests
*/
size_t get_active_request_count() const;
};
}

296
include/dpp/restrequest.h Normal file
View File

@ -0,0 +1,296 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/cluster.h>
#include <dpp/invite.h>
#include <dpp/json_fwd.h>
namespace dpp {
/**
* @brief Templated REST request helper to save on typing
*
* @tparam T type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param callback Callback lambda
*/
template<class T> inline void rest_request(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback) {
c->post_rest(basepath, major, minor, method, postdata, [c, callback](json &j, const http_request_completion_t& http) {
if (callback) {
callback(confirmation_callback_t(c, T().fill_from_json(&j), http));
}
});
};
/**
* @brief Templated REST request helper to save on typing (specialised for message)
*
* @tparam T type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param callback Callback lambda
*/
template<> inline void rest_request<message>(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback) {
c->post_rest(basepath, major, minor, method, postdata, [c, callback](json &j, const http_request_completion_t& http) {
if (callback) {
callback(confirmation_callback_t(c, message(c).fill_from_json(&j), http));
}
});
};
/**
* @brief Templated REST request helper to save on typing (specialised for confirmation)
*
* @tparam T type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param callback Callback lambda
*/
template<> inline void rest_request<confirmation>(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback) {
c->post_rest(basepath, major, minor, method, postdata, [c, callback](json &j, const http_request_completion_t& http) {
if (callback) {
callback(confirmation_callback_t(c, confirmation(), http));
}
});
};
/**
* @brief Templated REST request helper to save on typing (for returned lists)
*
* @tparam T singular type to return in lambda callback
* @tparam T map type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param key Key name of elements in the json list
* @param callback Callback lambda
*/
template<class T> inline void rest_request_list(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback, const std::string& key = "id") {
c->post_rest(basepath, major, minor, method, postdata, [c, key, callback](json &j, const http_request_completion_t& http) {
std::unordered_map<snowflake, T> list;
confirmation_callback_t e(c, confirmation(), http);
if (!e.is_error()) {
for (auto & curr_item : j) {
list[snowflake_not_null(&curr_item, key.c_str())] = T().fill_from_json(&curr_item);
}
}
if (callback) {
callback(confirmation_callback_t(c, list, http));
}
});
}
/**
* @brief Templated REST request helper to save on typing (for returned lists, specialised for invites)
*
* @tparam T singular type to return in lambda callback
* @tparam T map type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param key Key name of elements in the json list
* @param callback Callback lambda
*/
template<> inline void rest_request_list<invite>(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback, const std::string& key) {
c->post_rest(basepath, major, minor, method, postdata, [c, callback](json &j, const http_request_completion_t& http) {
invite_map list;
confirmation_callback_t e(c, confirmation(), http);
if (!e.is_error()) {
for (auto & curr_item : j) {
list[string_not_null(&curr_item, "code")] = invite().fill_from_json(&curr_item);
}
}
if (callback) {
callback(confirmation_callback_t(c, list, http));
}
});
}
/**
* @brief Templated REST request helper to save on typing (for returned lists, specialised for voiceregions)
*
* @tparam T singular type to return in lambda callback
* @tparam T map type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param key Key name of elements in the json list
* @param callback Callback lambda
*/
template<> inline void rest_request_list<voiceregion>(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback, const std::string& key) {
c->post_rest(basepath, major, minor, method, postdata, [c, callback](json &j, const http_request_completion_t& http) {
voiceregion_map list;
confirmation_callback_t e(c, confirmation(), http);
if (!e.is_error()) {
for (auto & curr_item : j) {
list[string_not_null(&curr_item, "id")] = voiceregion().fill_from_json(&curr_item);
}
}
if (callback) {
callback(confirmation_callback_t(c, list, http));
}
});
}
/**
* @brief Templated REST request helper to save on typing (for returned lists, specialised for bans)
*
* @tparam T singular type to return in lambda callback
* @tparam T map type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param key Key name of elements in the json list
* @param callback Callback lambda
*/
template<> inline void rest_request_list<ban>(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback, const std::string& key) {
c->post_rest(basepath, major, minor, method, postdata, [c, callback](json &j, const http_request_completion_t& http) {
std::unordered_map<snowflake, ban> list;
confirmation_callback_t e(c, confirmation(), http);
if (!e.is_error()) {
for (auto & curr_item : j) {
ban curr_ban = ban().fill_from_json(&curr_item);
list[curr_ban.user_id] = curr_ban;
}
}
if (callback) {
callback(confirmation_callback_t(c, list, http));
}
});
}
/**
* @brief Templated REST request helper to save on typing (for returned lists, specialised for sticker packs)
*
* @tparam T singular type to return in lambda callback
* @tparam T map type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param key Key name of elements in the json list
* @param callback Callback lambda
*/
template<> inline void rest_request_list<sticker_pack>(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback, const std::string& key) {
c->post_rest(basepath, major, minor, method, postdata, [c, key, callback](json &j, const http_request_completion_t& http) {
std::unordered_map<snowflake, sticker_pack> list;
confirmation_callback_t e(c, confirmation(), http);
if (!e.is_error()) {
if (j.contains("sticker_packs")) {
for (auto &curr_item: j["sticker_packs"]) {
list[snowflake_not_null(&curr_item, key.c_str())] = sticker_pack().fill_from_json(&curr_item);
}
}
}
if (callback) {
callback(confirmation_callback_t(c, list, http));
}
});
}
/**
* @brief Templated REST request helper to save on typing (for returned lists)
*
* @tparam T singular type to return in lambda callback
* @tparam T map type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param key Key name of elements in the json list
* @param root Root element to look for
* @param callback Callback lambda
*/
template<class T> inline void rest_request_list(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback, const std::string& key, const std::string& root) {
c->post_rest(basepath, major, minor, method, postdata, [c, root, key, callback](json &j, const http_request_completion_t& http) {
std::unordered_map<snowflake, T> list;
confirmation_callback_t e(c, confirmation(), http);
if (!e.is_error()) {
for (auto & curr_item : j[root]) {
list[snowflake_not_null(&curr_item, key.c_str())] = T().fill_from_json(&curr_item);
}
}
if (callback) {
callback(confirmation_callback_t(c, list, http));
}
});
}
/**
* @brief Templated REST request helper to save on typing (for returned lists, specialised for objects which doesn't have ids)
*
* @tparam T singular type to return in lambda callback
* @tparam T vector type to return in lambda callback
* @param c calling cluster
* @param basepath base path for API call
* @param major major API function
* @param minor minor API function
* @param method HTTP method
* @param postdata Post data or empty string
* @param callback Callback lambda
*/
template<class T> inline void rest_request_vector(dpp::cluster* c, const char* basepath, const std::string &major, const std::string &minor, http_method method, const std::string& postdata, command_completion_event_t callback) {
c->post_rest(basepath, major, minor, method, postdata, [c, callback](json &j, const http_request_completion_t& http) {
std::vector<T> list;
confirmation_callback_t e(c, confirmation(), http);
if (!e.is_error()) {
for (auto & curr_item : j) {
list.push_back(T().fill_from_json(&curr_item));
}
}
if (callback) {
callback(confirmation_callback_t(c, list, http));
}
});
}
}

342
include/dpp/restresults.h Normal file
View File

@ -0,0 +1,342 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <map>
#include <variant>
#include <dpp/snowflake.h>
#include <dpp/dispatcher.h>
#include <dpp/misc-enum.h>
#include <dpp/timer.h>
#include <dpp/json_fwd.h>
#include <dpp/discordclient.h>
#include <dpp/voiceregion.h>
#include <dpp/dtemplate.h>
#include <dpp/prune.h>
#include <dpp/auditlog.h>
#include <dpp/queues.h>
#include <dpp/cache.h>
#include <dpp/intents.h>
#include <algorithm>
#include <iostream>
#include <shared_mutex>
#include <cstring>
#include <dpp/entitlement.h>
#include <dpp/sku.h>
namespace dpp {
/**
* @brief A list of shards
*/
typedef std::map<uint32_t, class discord_client*> shard_list;
/**
* @brief List of shards awaiting reconnection, by id with earliest possible reconnect time
*/
typedef std::map<uint32_t, time_t> reconnect_list;
/**
* @brief Represents the various information from the 'get gateway bot' api call
*/
struct DPP_EXPORT gateway : public json_interface<gateway> {
protected:
friend struct json_interface<gateway>;
/**
* @brief Fill this object from json
*
* @param j json to fill from
* @return gateway& reference to self
*/
gateway& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief Gateway websocket url.
*/
std::string url;
/**
* @brief Number of suggested shards to start.
*/
uint32_t shards;
/**
* @brief Total number of sessions that can be started.
*/
uint32_t session_start_total;
/**
* @brief How many sessions are left.
*/
uint32_t session_start_remaining;
/**
* @brief How many seconds until the session start quota resets.
*/
uint32_t session_start_reset_after;
/**
* @brief How many sessions can be started at the same time.
*/
uint32_t session_start_max_concurrency;
/**
* @brief Construct a new gateway object
*
* @param j JSON data to construct from
*/
gateway(nlohmann::json* j);
/**
* @brief Construct a new gateway object
*/
gateway();
};
/**
* @brief Confirmation object represents any true or false simple REST request
*
*/
struct DPP_EXPORT confirmation {
bool success;
};
/**
* @brief A container for types that can be returned for a REST API call
*
*/
typedef std::variant<
active_threads,
application_role_connection,
application_role_connection_metadata_list,
confirmation,
message,
message_map,
user,
user_identified,
user_map,
guild_member,
guild_member_map,
channel,
channel_map,
thread_member,
thread_member_map,
guild,
guild_map,
guild_command_permissions,
guild_command_permissions_map,
role,
role_map,
invite,
invite_map,
dtemplate,
dtemplate_map,
emoji,
emoji_map,
ban,
ban_map,
voiceregion,
voiceregion_map,
voicestate,
integration,
integration_map,
webhook,
webhook_map,
prune,
guild_widget,
gateway,
interaction,
interaction_response,
auditlog,
slashcommand,
slashcommand_map,
stage_instance,
sticker,
sticker_map,
sticker_pack,
sticker_pack_map,
application,
application_map,
connection,
connection_map,
thread,
thread_map,
scheduled_event,
scheduled_event_map,
event_member,
event_member_map,
automod_rule,
automod_rule_map,
onboarding,
welcome_screen,
entitlement,
entitlement_map,
sku,
sku_map
> confirmable_t;
/**
* @brief The details of a field in an error response
*/
struct DPP_EXPORT error_detail {
/**
* @brief Object name which is in error
*/
std::string object;
/**
* @brief Field name which is in error
*/
std::string field;
/**
* @brief Error code
*/
std::string code;
/**
* @brief Error reason (full message)
*/
std::string reason;
/**
* @brief Object field index
*/
DPP_DEPRECATED("index is unused and will be removed in a future version") int index = 0;
};
/**
* @brief The full details of an error from a REST response
*/
struct DPP_EXPORT error_info {
/**
* @brief Error code
*/
uint32_t code = 0;
/**
* @brief Error message
*
*/
std::string message;
/**
* @brief Field specific error descriptions
*/
std::vector<error_detail> errors;
/**
* @brief Human readable error message constructed from the above
*/
std::string human_readable;
};
/**
* @brief The results of a REST call wrapped in a convenient struct
*/
struct DPP_EXPORT confirmation_callback_t {
/**
* @brief Information about the HTTP call used to make the request.
*/
http_request_completion_t http_info;
/**
* @brief Value returned, wrapped in variant.
*/
confirmable_t value;
/**
* @brief Owner/creator of the callback object.
*/
const class cluster* bot;
/**
* @brief Construct a new confirmation callback t object.
*/
confirmation_callback_t() = default;
/**
* @brief Construct a new confirmation callback t object
*
* @param creator owning cluster object
*/
confirmation_callback_t(cluster* creator);
/**
* @brief Construct a new confirmation callback object
*
* @param _http The HTTP metadata from the REST call
*/
confirmation_callback_t(const http_request_completion_t& _http);
/**
* @brief Construct a new confirmation callback object
*
* @param creator owning cluster object
* @param _value The value to encapsulate in the confirmable_t
* @param _http The HTTP metadata from the REST call
*/
confirmation_callback_t(cluster* creator, const confirmable_t& _value, const http_request_completion_t& _http);
/**
* @brief Returns true if the call resulted in an error rather than a legitimate value in the
* confirmation_callback_t::value member.
*
* @return true There was an error who's details can be obtained by get_error()
* @return false There was no error
*/
bool is_error() const;
/**
* @brief Get the error_info object.
* The error_info object contains the details of any REST error, if there is an error
* (to find out if there is an error check confirmation_callback_t::is_error())
*
* @return error_info The details of the error message
*/
error_info get_error() const;
/**
* @brief Get the stored value via std::get
* @tparam T type to get
* @return stored value as type T
*/
template<typename T>
T get() const {
return std::get<T>(value);
}
};
/**
* @brief A callback upon command completion
*/
typedef std::function<void(const confirmation_callback_t&)> command_completion_event_t;
/**
* @brief Automatically JSON encoded HTTP result
*/
typedef std::function<void(json&, const http_request_completion_t&)> json_encode_t;
}

1007
include/dpp/role.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,339 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/user.h>
#include <dpp/guild.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Represents the privacy of an event.
*/
enum event_privacy_level : uint8_t {
/**
* @brief The event is visible to only guild members.
*/
ep_guild_only = 2
};
/**
* @brief Event entity types.
*/
enum event_entity_type : uint8_t {
/**
* @brief A stage instance.
*/
eet_stage_instance = 1,
/**
* @brief A voice channel.
*/
eet_voice = 2,
/**
* @brief External to discord, or a text channel etc.
*/
eet_external = 3
};
/**
* @brief Event status types.
*/
enum event_status : uint8_t {
/**
* @brief Scheduled.
*/
es_scheduled = 1,
/**
* @brief Active now.
*/
es_active = 2,
/**
* @brief Completed.
*/
es_completed = 3,
/**
* @brief Cancelled.
*/
es_cancelled = 4
};
/**
* @brief Entities for the event.
*/
struct DPP_EXPORT event_entities {
/**
* @brief Location of the event.
*/
std::string location;
};
/**
* @brief Represents a guild member/user who has registered interest in an event.
*
*/
struct DPP_EXPORT event_member {
/**
* @brief Event ID associated with.
*/
snowflake guild_scheduled_event_id;
/**
* @brief User details of associated user.
*
*/
dpp::user user;
/**
* @brief Member details of user on the associated guild.
*/
dpp::guild_member member;
};
/**
* @brief A scheduled event.
*/
struct DPP_EXPORT scheduled_event : public managed, public json_interface<scheduled_event> {
protected:
friend struct json_interface<scheduled_event>;
/**
* @brief Serialise a scheduled_event object from json
*
* @return scheduled_event& a reference to self
*/
scheduled_event& fill_from_json_impl(const nlohmann::json* j);
/**
* @brief Build json for this object
* @param with_id Include id field in json
*
* @return std::string Json of this object
*/
json to_json_impl(bool with_id = false) const;
public:
/**
* @brief The guild ID which the scheduled event belongs to.
*/
snowflake guild_id;
/**
* @brief The channel ID in which the scheduled event will be hosted, or null if scheduled entity type is EXTERNAL.
*
* @note This may be empty.
*/
snowflake channel_id;
/**
* @brief Optional: The ID of the user that created the scheduled event.
*/
snowflake creator_id;
/**
* @brief The name of the scheduled event.
*/
std::string name;
/**
* @brief Optional: The description of the scheduled event (1-1000 characters).
*/
std::string description;
/**
* @brief The image of the scheduled event.
*
* @note This may be empty.
*/
utility::icon image;
/**
* @brief The time the scheduled event will start.
*/
time_t scheduled_start_time;
/**
* @brief The time the scheduled event will end, or null if the event does not have a scheduled time to end.
*
* @note This may be empty.
*/
time_t scheduled_end_time;
/**
* @brief The privacy level of the scheduled event.
*/
event_privacy_level privacy_level;
/**
* @brief The status of the scheduled event.
*/
event_status status;
/**
* @brief The type of hosting entity associated with a scheduled event.
* e.g. voice channel or stage channel.
*/
event_entity_type entity_type;
/**
* @brief Any additional ID of the hosting entity associated with event.
* e.g. stage instance ID.
*
* @note This may be empty.
*/
snowflake entity_id;
/**
* @brief The entity metadata for the scheduled event.
*
* @note This may be empty.
*/
event_entities entity_metadata;
/**
* @brief Optional: The creator of the scheduled event.
*/
user creator;
/**
* @brief Optional: The number of users subscribed to the scheduled event.
*/
uint32_t user_count;
/**
* @brief Create a scheduled_event object.
*/
scheduled_event();
/**
* @brief Set the name of the event.
* Minimum length: 1, Maximum length: 100
* @param n event name
* @return scheduled_event& reference to self
* @throw dpp::length_error if length < 1
*/
scheduled_event& set_name(const std::string& n);
/**
* @brief Set the description of the event.
* Minimum length: 1 (if set), Maximum length: 100
* @param d event description
* @return scheduled_event& reference to self
* @throw dpp::length_error if length < 1
*/
scheduled_event& set_description(const std::string& d);
/**
* @brief Clear the description of the event.
* @return scheduled_event& reference to self
*/
scheduled_event& clear_description();
/**
* @brief Set the location of the event.
* Minimum length: 1, Maximum length: 1000
* @note Clears channel_id
* @param l event location
* @return scheduled_event& reference to self
* @throw dpp::length_error if length < 1
*/
scheduled_event& set_location(const std::string& l);
/**
* @brief Set the voice channel id of the event.
* @note clears location
* @param c channel ID
* @return scheduled_event& reference to self
*/
scheduled_event& set_channel_id(snowflake c);
/**
* @brief Set the creator id of the event.
* @param c creator user ID
* @return scheduled_event& reference to self
*/
scheduled_event& set_creator_id(snowflake c);
/**
* @brief Set the status of the event.
* @param s status to set
* @return scheduled_event& reference to self
* @throw dpp::logic_exception if status change is not valid
*/
scheduled_event& set_status(event_status s);
/**
* @brief Set the start time of the event.
* @param t starting time
* @return scheduled_event& reference to self
* @throw dpp::length_error if time is before now
*/
scheduled_event& set_start_time(time_t t);
/**
* @brief Set the end time of the event.
* @param t ending time
* @return scheduled_event& reference to self
* @throw dpp::length_error if time is before now
*/
scheduled_event& set_end_time(time_t t);
/**
* @brief Load an image for the event cover.
*
* @param image_blob Image binary data
* @param type Type of image. It can be one of `i_gif`, `i_jpg` or `i_png`.
* @return emoji& Reference to self
*/
scheduled_event& load_image(std::string_view image_blob, const image_type type);
/**
* @brief Load an image for the event cover.
*
* @param data Image binary data
* @param size Size of the image.
* @param type Type of image. It can be one of `i_gif`, `i_jpg` or `i_png`.
* @return emoji& Reference to self
*/
scheduled_event& load_image(const std::byte* data, uint32_t size, const image_type type);
};
/**
* @brief A group of scheduled events.
*/
typedef std::unordered_map<snowflake, scheduled_event> scheduled_event_map;
/**
* @brief A group of scheduled event members.
*/
typedef std::unordered_map<snowflake, event_member> event_member_map;
} // namespace dpp

View File

@ -0,0 +1,53 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <mutex>
namespace dpp {
/**
* @brief Verifies signatures on incoming webhooks using OpenSSL
*/
class signature_verifier {
public:
/**
* @brief Constructor initializes the OpenSSL context and public key buffer
*/
signature_verifier();
/**
* @brief Verifies the signature with the provided public key, timestamp, body, and signature
* @param timestamp The timestamp of the request
* @param body The body of the request
* @param signature The hex-encoded signature to verify
* @param public_key_hex The hex-encoded public key
* @return true if the signature is valid, false otherwise
*/
bool verify_signature(const std::string& timestamp, const std::string& body, const std::string& signature, const std::string& public_key_hex);
};
}

176
include/dpp/sku.h Normal file
View File

@ -0,0 +1,176 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
#include <unordered_map>
namespace dpp {
/**
* @brief The type of SKU.
* */
enum sku_type : uint8_t {
/**
* @brief Represents a durable one-time purchase
*/
DURABLE = 2,
/**
* @brief Consumable one-time purchase
*/
CONSUMABLE = 3,
/**
* @brief Represents a recurring subscription
*/
SUBSCRIPTION = 5,
/**
* @brief System-generated group for each SUBSCRIPTION SKU created
* @warning These are automatically created for each subscription SKU and are not used at this time. Please refrain from using these.
*/
SUBSCRIPTION_GROUP = 6,
};
/**
* @brief SKU flags.
*/
enum sku_flags : uint16_t {
/**
* @brief SKU is available for purchase
*/
sku_available = 0b000000000000100,
/**
* @brief Recurring SKU that can be purchased by a user and applied to a single server. Grants access to every user in that server.
*/
sku_guild_subscription = 0b000000010000000,
/**
* @brief Recurring SKU purchased by a user for themselves. Grants access to the purchasing user in every server.
*/
sku_user_subscription = 0b000000100000000,
};
/**
* @brief A definition of a discord SKU.
*/
class DPP_EXPORT sku : public managed, public json_interface<sku> {
protected:
friend struct json_interface<sku>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
sku& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build json for this SKU object
*
* @param with_id include the ID in the json
* @return json JSON object
*/
json to_json_impl(bool with_id = false) const;
public:
/**
* @brief The type of SKU.
*/
sku_type type{sku_type::SUBSCRIPTION};
/**
* @brief ID of the parent application
*/
snowflake application_id{0};
/**
* @brief Customer-facing name of your premium offering
*/
std::string name{};
/**
* @brief System-generated URL slug based on the SKU's name
*/
std::string slug{};
/**
* @brief Flags bitmap from dpp::sku_flags
*/
uint16_t flags{0};
/**
* @brief Construct a new SKU object
*/
sku() = default;
/**
* @brief Construct a new SKU object with all data required.
*
* @param id ID of the SKU.
* @param type Type of SKU (sku_type).
* @param application_id ID of the parent application.
* @param name Customer-facing name of your premium offering.
* @param slug System-generated URL slug based on the SKU's name.
* @param flags Flags bitmap from dpp::sku_flags.
*
*/
sku(const snowflake id, const sku_type type, const snowflake application_id, const std::string name, const std::string slug, const uint16_t flags);
/**
* @brief Get the type of SKU.
*
* @return sku_type SKU type
*/
sku_type get_type() const;
/**
* @brief Is the SKU available for purchase?
*
* @return true if the SKU can be purchased.
*/
bool is_available() const;
/**
* @brief Is the SKU a guild subscription?
*
* @return true if the SKU is a guild subscription.
*/
bool is_guild_subscription() const;
/**
* @brief Is the SKU a user subscription?
*
* @return true if the SKU is a user subscription
*/
bool is_user_subscription() const;
};
/**
* @brief Group of SKUs.
*/
typedef std::unordered_map<snowflake, sku> sku_map;
}

307
include/dpp/snowflake.h Normal file
View File

@ -0,0 +1,307 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/json_fwd.h>
#include <dpp/exception.h>
#include <cstdint>
#include <type_traits>
#ifdef DPP_FORMATTERS
#include <format>
#endif
/**
* @brief The main namespace for D++ functions. classes and types
*/
namespace dpp {
/** @brief A container for a 64 bit unsigned value representing many things on discord.
* This value is known in distributed computing as a snowflake value.
*
* Snowflakes are:
*
* - Performant (very fast to generate at source and to compare in code)
* - Uncoordinated (allowing high availability across clusters, data centres etc)
* - Time ordered (newer snowflakes have higher IDs)
* - Directly Sortable (due to time ordering)
* - Compact (64 bit numbers, not 128 bit, or string)
*
* An identical format of snowflake is used by Twitter, Instagram and several other platforms.
*
* @see https://en.wikipedia.org/wiki/Snowflake_ID
* @see https://github.com/twitter-archive/snowflake/tree/b3f6a3c6ca8e1b6847baa6ff42bf72201e2c2231
*/
class DPP_EXPORT snowflake final {
friend struct std::hash<dpp::snowflake>;
protected:
/**
* @brief The snowflake value
*/
uint64_t value = 0;
public:
/**
* @brief Construct a snowflake object
*/
constexpr snowflake() noexcept = default;
/**
* @brief Copy a snowflake object
*/
constexpr snowflake(const snowflake &rhs) noexcept = default;
/**
* @brief Move a snowflake object
*/
constexpr snowflake(snowflake &&rhs) noexcept = default;
/**
* @brief Construct a snowflake from an integer value
*
* @throw dpp::logic_exception on assigning a negative value. The function is noexcept if the type given is unsigned
* @param snowflake_val snowflake value as an integer type
*/
template <typename T, typename = std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>>>
constexpr snowflake(T snowflake_val) noexcept(std::is_unsigned_v<T>) : value(static_cast<std::make_unsigned_t<T>>(snowflake_val)) {
/**
* we cast to the unsigned version of the type given - this maintains "possible loss of data" warnings for sizeof(T) > sizeof(value)
* while suppressing them for signed to unsigned conversion (for example snowflake(42) will call snowflake(int) which is a signed type)
*/
if constexpr (!std::is_unsigned_v<T>) {
/* if the type is signed, at compile-time, add a check at runtime that the value is unsigned */
if (snowflake_val < 0) {
value = 0;
throw dpp::logic_exception{"cannot assign a negative value to dpp::snowflake"};
}
}
}
/**
* @brief Construct a snowflake object from an unsigned integer in a string
*
* On invalid string the value will be 0
* @param string_value A snowflake value
*/
snowflake(std::string_view string_value) noexcept;
/**
* @brief Construct a snowflake object from an unsigned integer in a string
*
* On invalid string the value will be 0
* @param string_value A snowflake value
*/
template <typename T, typename = std::enable_if_t<std::is_same_v<T, std::string>>>
snowflake(const T &string_value) noexcept : snowflake(std::string_view{string_value}) {}
/* ^ this exists to preserve `message_cache.find(std::get<std::string>(event.get_parameter("message_id")));` */
/**
* @brief Copy value from another snowflake
*
* @param rhs The snowflake to copy from
*/
constexpr dpp::snowflake &operator=(const dpp::snowflake& rhs) noexcept = default;
/**
* @brief Move value from another snowflake
*
* @param rhs The snowflake to move from
*/
constexpr dpp::snowflake &operator=(dpp::snowflake&& rhs) noexcept = default;
/**
* @brief Assign integer value to the snowflake
*
* @throw dpp::logic_exception on assigning a negative value. The function is noexcept if the type given is unsigned
* @param snowflake_val snowflake value as an integer type
*/
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
constexpr dpp::snowflake &operator=(T snowflake_val) noexcept(std::is_unsigned_v<T>) {
return *this = dpp::snowflake{snowflake_val};
}
/**
* @brief Assign value converted from a string to the snowflake
*
* On invalid string the value will be 0
* @param snowflake_val snowflake value as a string
*/
template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::string_view>>>
constexpr dpp::snowflake &operator=(T&& snowflake_val) noexcept {
return *this = dpp::snowflake{std::forward<T>(snowflake_val)};
}
/**
* @brief Returns true if the snowflake holds an empty value (is 0)
*
* @return true if empty (zero)
*/
constexpr bool empty() const noexcept {
return value == 0;
}
/**
* @brief Returns the stringified version of the snowflake value
*
* @return std::string string form of snowflake value
*/
inline std::string str() const {
return std::to_string(value);
}
/**
* @brief Comparison operator with another snowflake
*
* @param snowflake_val snowflake
*/
constexpr bool operator==(dpp::snowflake snowflake_val) const noexcept {
return value == snowflake_val.value;
}
/**
* @brief Comparison operator with a string
*
* @param snowflake_val snowflake value as a string
*/
bool operator==(std::string_view snowflake_val) const noexcept;
/**
* @brief Comparison operator with an integer
*
* @param snowflake_val snowflake value as an integer type
*/
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
constexpr bool operator==(T snowflake_val) const noexcept {
/* We use the std::enable_if_t trick to disable implicit conversions so there is a perfect candidate for overload resolution for integers, and it isn't ambiguous */
return *this == dpp::snowflake{snowflake_val};
}
/**
* @brief For acting like an integer
* @return The snowflake value
*/
constexpr operator uint64_t() const noexcept {
return value;
}
/**
* @brief For acting like an integer
* @return A reference to the snowflake value
*/
constexpr operator uint64_t &() noexcept {
return value;
}
/**
* @brief For building json
* @return The snowflake value as a string
*/
operator json() const;
/**
* @brief Get the creation time of this snowflake according to Discord.
*
* @return double creation time inferred from the snowflake ID.
* The minimum possible value is the first second of 2015.
*/
constexpr double get_creation_time() const noexcept {
constexpr uint64_t first_january_2016 = 1420070400000ull;
return static_cast<double>((value >> 22) + first_january_2016) / 1000.0;
}
/**
* @brief Get the worker id that produced this snowflake value
*
* @return uint8_t worker id
*/
constexpr uint8_t get_worker_id() const noexcept {
return static_cast<uint8_t>((value & 0x3E0000) >> 17);
}
/**
* @brief Get the process id that produced this snowflake value
*
* @return uint8_t process id
*/
constexpr uint8_t get_process_id() const noexcept {
return static_cast<uint8_t>((value & 0x1F000) >> 12);
}
/**
* @brief Get the increment, which is incremented for every snowflake
* created over the one millisecond resolution in the timestamp.
*
* @return uint64_t millisecond increment
*/
constexpr uint16_t get_increment() const noexcept {
return static_cast<uint16_t>(value & 0xFFF);
}
/**
* @brief Helper function for libfmt so that a snowflake can be directly formatted as a uint64_t.
*
* @see https://fmt.dev/latest/api.html#formatting-user-defined-types
* @return uint64_t snowflake ID
*/
friend constexpr uint64_t format_as(snowflake s) noexcept {
/* note: this function must stay as "friend" - this declares it as a free function but makes it invisible unless the argument is snowflake
* this effectively means no implicit conversions are performed to snowflake, for example format_as(0) doesn't call this function */
return s.value;
}
};
}
template<>
struct std::hash<dpp::snowflake>
{
/**
* @brief Hashing function for dpp::snowflake
* Used by std::unordered_map. This just calls std::hash<uint64_t>.
*
* @param s Snowflake value to hash
* @return std::size_t hash value
*/
std::size_t operator()(dpp::snowflake s) const noexcept {
return std::hash<uint64_t>{}(s.value);
}
};
#ifdef DPP_FORMATTERS
/*
* @brief implementation of formater for dpp::snowflake for std::format support
* https://en.cppreference.com/w/cpp/utility/format/formatter
*/
template <>
struct std::formatter<dpp::snowflake>
{
template<class TP>
constexpr typename TP::iterator parse(TP& ctx) {
return ctx.begin();
}
template<class TF>
typename TF::iterator format(const dpp::snowflake& snowflake, TF& ctx) const {
return std::format_to(ctx.out(), "{}", snowflake.str());
}
};
#endif //DPP_FORMATTERS

196
include/dpp/socket.h Normal file
View File

@ -0,0 +1,196 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#ifdef _WIN32
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <io.h>
#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
#define pollfd WSAPOLLFD
#else
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
#include <string_view>
#include <cstdint>
namespace dpp
{
/**
* @brief Represents a socket file descriptor.
* This is used to ensure parity between windows and unix-like systems.
*/
#ifndef _WIN32
using socket = int;
#else
using socket = SOCKET;
#endif
#ifndef SOCKET_ERROR
/**
* @brief Represents a socket in error state
*/
#define SOCKET_ERROR -1
#endif
#ifndef INVALID_SOCKET
/**
* @brief Represents a socket which is not yet assigned
*/
#define INVALID_SOCKET ~0
#endif
/**
* @brief Represents an IPv4 address for use with socket functions such as
* bind().
*
* Avoids type punning with C style casts from sockaddr_in to sockaddr pointers.
*/
class DPP_EXPORT address_t {
/**
* @brief Internal sockaddr struct
*/
sockaddr socket_addr{};
public:
/**
* @brief Create a new address_t
* @param ip IPv4 address
* @param port Port number
* @note Leave both as defaults to create a default bind-to-any setting
*/
address_t(const std::string_view ip = "0.0.0.0", uint16_t port = 0);
/**
* @brief Get sockaddr
* @return sockaddr pointer
*/
[[nodiscard]] sockaddr *get_socket_address();
/**
* @brief Returns size of sockaddr_in
* @return sockaddr_in size
* @note It is important the size this returns is sizeof(sockaddr_in) not
* sizeof(sockaddr), this is NOT a bug but requirement of C socket functions.
*/
[[nodiscard]] size_t size();
/**
* @brief Get the port bound to a file descriptor
* @param fd File descriptor
* @return Port number, or 0 if no port bound
*/
[[nodiscard]] uint16_t get_port(socket fd);
};
enum raii_socket_type {
rst_udp,
rst_tcp,
};
/**
* @brief Allocates a dpp::socket, closing it on destruction
*/
struct DPP_EXPORT raii_socket {
/**
* @brief File descriptor
*/
socket fd;
/**
* @brief Construct a socket.
* Calls socket() and returns a new file descriptor
*/
raii_socket(raii_socket_type type = rst_udp);
/**
* @brief Convert an established fd to an raii_socket
* @param plain_fd
*/
raii_socket(socket plain_fd);
/**
* @brief Non-copyable
*/
raii_socket(raii_socket&) = delete;
/**
* @brief Non-movable
*/
raii_socket(raii_socket&&) = delete;
/**
* @brief Sets the value of a socket option.
* @tparam T type to set option for
* @param level The level at which to change the socket options
* @param name The option to change the value of
* @param value The value to set
* @return True if set successfully
*/
template <typename T> bool set_option(int level, int name, T value);
/**
* @brief Bind socket to IP/port
* @param address address to bind to
* @return true on success
*/
bool bind(address_t address);
/**
* @brief Listen on previously bound port
* @return true on success
*/
bool listen();
/**
* @brief Accept a pending connection on listening socket
* @return new connection file descriptor
*/
socket accept();
/**
* @brief Non-copyable
*/
raii_socket operator=(raii_socket&) = delete;
/**
* @brief Non-movable
*/
raii_socket operator=(raii_socket&&) = delete;
/**
* @brief Destructor
* Frees the socket by closing it
*/
~raii_socket();
};
extern template DPP_EXPORT bool raii_socket::set_option<int>(int level, int name, int value);
}

View File

@ -0,0 +1,154 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/cluster.h>
#include <dpp/socket.h>
#include <dpp/sslconnection.h>
#include <type_traits>
#include <memory>
#include <unordered_map>
#include <string_view>
#include <string>
namespace dpp {
enum socket_listener_type : uint8_t {
li_plaintext,
li_ssl,
};
/**
* @brief Listens on a TCP socket for new connections, and whenever a new connection is
* received, accept it and spawn a new connection of type T.
* @tparam T type for socket connection, must be derived from ssl_connection
*/
template<typename T, typename = std::enable_if_t<std::is_base_of_v<ssl_connection, T>>>
struct socket_listener {
/**
* @brief The listening socket for incoming connections
*/
raii_socket fd;
/**
* @brief Active connections for the server of type T
*/
std::unordered_map<socket, std::unique_ptr<T>> connections;
/**
* @brief Cluster creator
*/
cluster* creator{nullptr};
/**
* @brief True if plain text connections to the server are allowed
*/
bool plaintext{true};
/**
* @brief Private key PEM file path, if running an SSL server
*/
std::string private_key_file;
/**
* @brief Public key PEM file path, if running an SSL server
*/
std::string public_key_file;
/**
* @brief Event to handle socket removal from the connection map
*/
event_handle close_event;
/**
* @brief Socket events for listen socket in the socket engine
*/
socket_events events;
/**
* @brief Create a new socket listener (TCP server)
* @param owner Owning cluster
* @param address IP address to bind the listening socket to, use 0.0.0.0 to bind all interfaces
* @param port Port number to bind the listening socket to
* @param type Type of server, plaintext or SSL
* @param private_key For SSL servers, a path to the PEM private key file
* @param public_key For SSL servers, a path to the PEM public key file
* @throws connection_exception on failure to bind or listen to the port/interface
*/
socket_listener(cluster* owner, const std::string_view address, uint16_t port, socket_listener_type type = li_plaintext, const std::string& private_key = "", const std::string& public_key = "")
: fd(rst_tcp), creator(owner), plaintext(type == li_plaintext), private_key_file(private_key), public_key_file(public_key)
{
fd.set_option<int>(SOL_SOCKET, SO_REUSEADDR, 1);
if (!fd.bind(address_t(address, port))) {
// error
throw dpp::connection_exception("Could not bind to " + std::string(address) + ":" + std::to_string(port));
}
if (!fd.listen()) {
// error
throw dpp::connection_exception("Could not listen for connections on " + std::string(address) + ":" + std::to_string(port));
}
events = dpp::socket_events(
fd.fd,
WANT_READ | WANT_ERROR,
[this](socket sfd, const struct socket_events &e) {
handle_accept(sfd, e);
},
[](socket, const struct socket_events&) { },
[](socket, const struct socket_events&, int) { }
);
owner->socketengine->register_socket(events);
close_event = creator->on_socket_close([this](const socket_close_t& event) {
connections.erase(event.fd);
});
}
/**
* @brief Destructor, detaches on_socket_close event
*/
~socket_listener() {
creator->on_socket_close.detach(close_event);
}
/**
* @brief Handle a new incoming socket with accept()
* Accepts a new connection, and calls emplace() if valid
* @param sfd File descriptor for listening socket
* @param e socket events for the listening socket
*/
virtual void handle_accept(socket sfd, const struct socket_events &e) {
socket new_fd{fd.accept()};
if (new_fd >= 0) {
emplace(new_fd);
}
}
/**
* @brief Emplace a new connection into the connection map for the server.
* This is a factory function which must be implemented by the deriving class
* @param newfd File descriptor for new connection
*/
virtual void emplace(socket newfd) = 0;
};
}

332
include/dpp/socketengine.h Normal file
View File

@ -0,0 +1,332 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/socket.h>
#include <cstdint>
#include <unordered_map>
#include <memory>
#include <string_view>
#include <functional>
#include <shared_mutex>
#include <dpp/thread_pool.h>
namespace dpp {
/**
* @brief Types of IO events a socket may subscribe to.
*/
enum socket_event_flags : uint8_t {
/**
* @brief Socket wants to receive events when it can be read from.
* This is provided by the underlying implementation.
*/
WANT_READ = 1,
/**
* @brief Socket wants to receive events when it can be written to.
* This is provided by the underlying implementation, and will be
* a one-off event. If you want to receive ongoing write events you
* must re-request this event type each time.
*/
WANT_WRITE = 2,
/**
* @brief Socket wants to receive events that indicate an error condition.
* Note that EOF (graceful close) is not an error condition and is indicated
* by errno being 0 and ::read() returning 0.
*/
WANT_ERROR = 4,
/**
* @brief Socket should be removed as soon as is safe to do so. Generally, this is
* after the current iteration through the active event list.
*/
WANT_DELETION = 8,
};
/**
* @brief Read ready event
*/
using socket_read_event = std::function<void(dpp::socket fd, const struct socket_events&)>;
/**
* @brief Write ready event
*/
using socket_write_event = std::function<void(dpp::socket fd, const struct socket_events&)>;
/**
* @brief Error event
*/
using socket_error_event = std::function<void(dpp::socket fd, const struct socket_events&, int error_code)>;
/**
* @brief Contains statistics about the IO loop
*/
struct DPP_EXPORT socket_stats {
/**
* @brief Number of reads since startup
*/
uint64_t reads{0};
/**
* @brief Number of writes since startup
*/
uint64_t writes{0};
/**
* @brief Number of errors since startup
*/
uint64_t errors{0};
/**
* @brief Number of updates to file descriptors
*/
uint64_t updates{0};
/**
* @brief Number of deletions of file descriptors
*/
uint64_t deletions{0};
/**
* @brief Number of loop iterations since startup
*/
uint64_t iterations{0};
/**
* @brief Number of currently active file descriptors
*/
uint64_t active_fds{0};
/**
* @brief Socket engine type
*/
std::string_view engine_type;
};
/**
* @brief Represents an active socket event set in the socket engine.
*
* An event set contains a file descriptor, a set of event handler callbacks, and
* a set of bitmask flags which indicate which events it wants to receive.
* It is possible to quickly toggle event types on or off, as it is not always necessary
* or desired to receive all events all the time, in fact doing so can cause an event
* storm which will consume 100% CPU (e.g. if you request to receive write events all
* the time).
*/
struct DPP_EXPORT socket_events {
/**
* @brief File descriptor
*
* This should be a valid file descriptor created via ::socket().
*/
dpp::socket fd{INVALID_SOCKET};
/**
* @brief Flag bit mask of values from dpp::socket_event_flags
*/
uint8_t flags{0};
/**
* @brief Read ready event
* @note This function will be called from a different thread to that
* which adds the event set to the socket engine.
*/
socket_read_event on_read{};
/**
* @brief Write ready event
* @note This function will be called from a different thread to that
* which adds the event set to the socket engine.
*/
socket_write_event on_write{};
/**
* @brief Error event
* @note This function will be called from a different thread to that
* which adds the event set to the socket engine.
*/
socket_error_event on_error{};
/**
* @brief Construct a new socket_events
* @param socket_fd file descriptor
* @param _flags initial flags bitmask
* @param read_event read ready event
* @param write_event write ready event
* @param error_event error event
*/
socket_events(dpp::socket socket_fd, uint8_t _flags, const socket_read_event& read_event, const socket_write_event& write_event = {}, const socket_error_event& error_event = {})
: fd(socket_fd), flags(_flags), on_read(read_event), on_write(write_event), on_error(error_event) { }
/**
* @brief Default constructor
*/
socket_events() = default;
};
/**
* @brief Container of event sets keyed by socket file descriptor
*/
using socket_container = std::unordered_map<dpp::socket, std::unique_ptr<socket_events>>;
/**
* @brief This is the base class for socket engines.
* The actual implementation is OS specific and the correct implementation is detected by
* CMake. It is then compiled specifically into DPP so only one implementation can exist
* in the implementation. All implementations should behave identically to the user, abstracting
* out implementation-specific behaviours (e.g. difference between edge and level triggered
* event mechanisms etc).
*/
struct DPP_EXPORT socket_engine_base {
/**
* @brief Owning cluster
*/
class cluster* owner{nullptr};
/**
* @brief Default constructor
* @param creator Owning cluster
*/
socket_engine_base(class cluster* creator);
/**
* @brief Non-copyable
*/
socket_engine_base(const socket_engine_base&) = delete;
/**
* @brief Non-copyable
*/
socket_engine_base(socket_engine_base&&) = delete;
/**
* @brief Non-movable
*/
socket_engine_base& operator=(const socket_engine_base&) = delete;
/**
* @brief Non-movable
*/
socket_engine_base& operator=(socket_engine_base&&) = delete;
/**
* @brief Default destructor
*/
virtual ~socket_engine_base() = default;
/**
* @brief Should be called repeatedly in a loop.
* Will run for a maximum of 1 second.
*/
virtual void process_events() = 0;
/**
* @brief Register a new socket with the socket engine
* @param e Socket events
* @return true if socket was added
*/
virtual bool register_socket(const socket_events& e);
/**
* @brief Update an existing socket in the socket engine
* @param e Socket events
* @return true if socket was updated
*/
virtual bool update_socket(const socket_events& e);
/**
* @brief Delete a socket from the socket engine
* @note This will not remove the socket immediately. It will set the
* WANT_DELETION flag causing it to be removed as soon as is safe to do so
* (once all events associated with it are completed).
* @param e File descriptor
* @return true if socket was queued for deletion
*/
bool delete_socket(dpp::socket fd);
/**
* @brief Iterate through the list of sockets and remove any
* with WANT_DELETION set. This will also call implementation-specific
* remove_socket() on each entry to be removed.
*/
void prune();
/**
* @brief Merge new flags in with the given file descriptor
* @param fd file descriptor
* @param extra_flags extra flags to add
*/
void inplace_modify_fd(dpp::socket fd, uint8_t extra_flags);
/**
* @brief Get statistics for socket engine
* @return socket stats
*/
const socket_stats& get_stats() const;
protected:
/**
* @brief Mutex for fds
*/
std::shared_mutex fds_mutex;
/**
* @brief File descriptors, and their states
*/
socket_container fds;
/**
* @brief Socket engine statistics
*/
socket_stats stats{};
/**
* @brief Find a file descriptors socket events
* @param fd file descriptor
* @return file descriptor or nullptr if doesn't exist
*/
socket_events* get_fd(dpp::socket fd);
/**
* @brief Called by the prune() function to remove sockets when safe to do so.
* This is normally at the end or before an iteration of the event loop.
* @param fd File descriptor to remove
*/
virtual bool remove_socket(dpp::socket fd);
};
/**
* @brief This is implemented by whatever derived form socket_engine takes
* @param creator Creating cluster
*/
DPP_EXPORT std::unique_ptr<socket_engine_base> create_socket_engine(class cluster *creator);
#ifndef _WIN32
/**
* @brief Set up a signal handler to be ignored
* @param signal Signal to set. If the signal is already set up with a handler,
* this will do nothing.
*/
void set_signal_handler(int signal);
#endif
};

53
include/dpp/ssl_context.h Normal file
View File

@ -0,0 +1,53 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <cstdint>
namespace dpp::detail {
struct wrapped_ssl_ctx;
/**
* @brief Generate a new wrapped SSL context.
* If an SSL context already exists for the given port number, it will be returned, else a new one will be
* generated and cached. Contexts with port = 0 will be considered client contexts. There can only be one
* client context at a time and it covers all SSL client connections. There can be many SSL server contexts,
* individual ones can be cached per-port, each with their own loaded SSL private and public key PEM certificate.
*
* @param port Port number. Pass zero to create or get the client context.
* @param private_key Private key PEM pathname for server contexts
* @param public_key Public key PEM pathname for server contexts
* @return wrapped SSL context
*/
DPP_EXPORT wrapped_ssl_ctx* generate_ssl_context(uint16_t port = 0, const std::string &private_key = "", const std::string &public_key = "");
/**
* @brief Release an SSL context
* @warning Only do this if you are certain no SSL connections remain that use this context.
* As OpenSSL is a C library it is impossible for us to track this on its behalf. Be careful!
* @param port port number to release
*/
DPP_EXPORT void release_ssl_context(uint16_t port = 0);
};

403
include/dpp/sslconnection.h Normal file
View File

@ -0,0 +1,403 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/misc-enum.h>
#include <string>
#include <functional>
#include <ctime>
#include <mutex>
#include <dpp/socket.h>
#include <cstdint>
#include <dpp/timer.h>
namespace dpp {
/**
* @brief This is an opaque class containing openssl library specific structures.
* We define it this way so that the public facing D++ library doesn't require
* the openssl headers be available to build against it.
*/
class openssl_connection;
/**
* @brief Close a socket
*
* @param sfd Socket to close
* @return false on error, true on success
*/
DPP_EXPORT bool close_socket(dpp::socket sfd);
/**
* @brief Set a socket to blocking or non-blocking IO
*
* @param sockfd socket to act upon
* @param non_blocking should socket be non-blocking?
* @return false on error, true on success
*/
DPP_EXPORT bool set_nonblocking(dpp::socket sockfd, bool non_blocking);
/**
* @brief SSL_read buffer size
*
* You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each.
* SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as
* it'd go unused.
*/
constexpr uint16_t DPP_BUFSIZE{16 * 1024};
/**
* @brief Represents a failed socket system call, e.g. connect() failure
*/
constexpr int ERROR_STATUS{-1};
/**
* @brief Maximum number of internal connect() retries on TCP connections
*/
constexpr int MAX_RETRIES{4};
/**
* @brief Implements a simple non-blocking SSL stream connection.
*/
class DPP_EXPORT ssl_connection
{
private:
/**
* @brief Clean up resources
*/
void cleanup();
/**
* @brief Mutex for creation of internal SSL pointers by openssl
*/
std::mutex ssl_mutex;
/**
* @brief Mutex for output buffer
*/
std::mutex out_mutex;
/**
* @brief Start offset into internal ring buffer for client to server IO
*/
size_t client_to_server_length = 0;
/**
* @brief Start offset into internal ring buffer for server to client IO
*/
size_t client_to_server_offset = 0;
/**
* @brief Internal ring buffer for client to server IO
*/
char client_to_server_buffer[DPP_BUFSIZE];
/**
* @brief Internal ring buffer for server to client IO
*/
char server_to_client_buffer[DPP_BUFSIZE];
/**
* @brief True if this connection is a server inbound connection from accept()
*/
bool is_server = false;
protected:
/**
* @brief Input buffer received from socket
*/
std::string buffer;
/**
* @brief Output buffer for sending to socket
*/
std::string obuffer;
/**
* @brief Raw file descriptor of connection
*/
dpp::socket sfd;
/**
* @brief Openssl opaque contexts
*/
openssl_connection* ssl;
/**
* @brief SSL cipher in use
*/
std::string cipher;
/**
* @brief For timers
*/
time_t last_tick;
/**
* @brief Start time of connection
*/
time_t start;
/**
* @brief How many times we retried connect()
*/
uint8_t connect_retries{0};
/**
* @brief Hostname connected to
*/
std::string hostname;
/**
* @brief Port connected to
*/
std::string port;
/**
* @brief Bytes out
*/
uint64_t bytes_out;
/**
* @brief Bytes in
*/
uint64_t bytes_in;
/**
* @brief True for a plain text connection
*/
bool plaintext;
/**
* @brief True if connection is completed
*/
bool connected{false};
/**
* @brief True if tcp connect() succeeded
*/
bool tcp_connect_done{false};
/**
* @brief Timer handle for one second timer
*/
timer timer_handle;
/**
* @brief Unique ID of socket used as a nonce
* You can use this to identify requests vs reply
* if you want. D++ itself only sets this, and does
* not use it in any logic. It starts at 1 and increments
* for each request made.
*/
uint64_t unique_id;
/**
* @brief Called every second
*/
virtual void one_second_timer();
/**
* @brief Start SSL connection and connect to TCP endpoint
* @throw dpp::exception Failed to initialise connection
*/
virtual void connect();
/**
* @brief Set this to true to log all IO to debug for this connection.
* This is an internal developer facility. Do not enable it unless you
* need to, as it will be very noisy.
*/
bool raw_trace{false};
/**
* @brief If raw_trace is set to true, log a debug message for this connection
* @param message debug message
*/
void do_raw_trace(const std::string& message) const;
virtual void on_buffer_drained();
/**
* @brief Start connecting to a TCP socket.
* This simply calls connect() and checks for error return, as the timeout is now handled in the main
* IO events for the ssl_connection class.
*
* @param sockfd socket descriptor
* @param addr address to connect to
* @param addrlen address length
* @param timeout_ms timeout in milliseconds
* @return int -1 on error, 0 on success just like POSIX connect()
* @throw dpp::connection_exception on failure
*/
int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen);
public:
/**
* @brief For low-level debugging, calling this function will
* enable low level I/O logging for this connection to the logger.
* This can be very loud, and output a lot of data, so only enable it
* selectively where you need it.
*
* Generally, you won't need this, it is a library development utility.
*/
void enable_raw_tracing();
/**
* @brief Get the bytes out objectGet total bytes sent
* @return uint64_t bytes sent
*/
uint64_t get_bytes_out();
/**
* @brief Get total bytes received
* @return uint64_t bytes received
*/
uint64_t get_bytes_in();
/**
* @brief Every request made has a unique ID. This increments
* for every request, starting at 1. You can use this for statistics,
* or to associate requests and replies in external event loops.
* @return Unique ID
*/
uint64_t get_unique_id() const;
/**
* @brief Get SSL cipher name
* @return std::string ssl cipher name
*/
std::string get_cipher();
/**
* @brief True if we are keeping the connection alive after it has finished
*/
bool keepalive;
/**
* @brief Owning cluster
*/
class cluster* owner;
/**
* @brief Private key PEM file path for inbound SSL connections
*/
std::string private_key_file;
/**
* @brief Public key PEM file path for inbound SSL connections
*/
std::string public_key_file;
/**
* @brief Connect to a specified host and port. Throws std::runtime_error on fatal error.
* @param creator Creating cluster
* @param _hostname The hostname to connect to
* @param _port the Port number to connect to
* @param plaintext_downgrade Set to true to connect using plaintext only, without initialising SSL.
* @param reuse Attempt to reuse previous connections for this hostname and port, if available
* Note that no Discord endpoints will function when downgraded. This option is provided only for
* connection to non-Discord addresses such as within dpp::cluster::request().
* @throw dpp::exception Failed to initialise connection
*/
ssl_connection(cluster* creator, const std::string &_hostname, const std::string &_port = "443", bool plaintext_downgrade = false, bool reuse = false);
/**
* @brief Accept a new connection from listen()/accept() socket
* @param creator Creating cluster
* @param fd Socket file descriptor assigned by accept()
* @param port Port the new fd came from
* @param plaintext_downgrade Set to true to connect using plaintext only, without initialising SSL.
* @param private_key if plaintext_downgrade is set to false, a private key PEM file for SSL connections
* @param public_key if plaintext_downgrade is set to false, a public key PEM file for SSL connections
*/
ssl_connection(cluster* creator, socket fd, uint16_t port, bool plaintext_downgrade = false, const std::string& private_key = "", const std::string& public_key = "");
/**
* @brief Set up non blocking I/O and configure on_read, on_write and on_error.
* @throw std::exception Any std::exception (or derivative) thrown from read_loop() indicates setup failed
*/
void read_loop();
/**
* @brief Destroy the ssl_connection object
*/
virtual ~ssl_connection();
/**
* @brief Handle input from the input buffer. This function will be called until
* all data in the buffer has been processed and the buffer is empty.
* @param buffer the buffer content. Will be modified removing any processed front elements
* @return bool True if the socket should remain connected
*/
virtual bool handle_buffer(std::string &buffer);
/**
* @brief Write to the output buffer.
* @param data Data to be written to the buffer.
* @note The data may not be written immediately and may be written at a later time to the socket.
*/
void socket_write(const std::string_view data);
/**
* @brief Close socket connection
*/
virtual void close();
/**
* @brief Log a message
* @param severity severity of log message
* @param msg Log message to send
*/
virtual void log(dpp::loglevel severity, const std::string &msg) const;
/**
* @brief Called while SSL handshake is in progress.
* If the handshake completes, the state of the socket is progressed to
* an established state.
* @param ev Socket events for the socket
*/
void complete_handshake(const struct socket_events* ev);
/**
* @brief Called when the TCP socket has data to read
* @param fd File descriptor
* @param ev Socket events
*/
void on_read(dpp::socket fd, const struct dpp::socket_events& ev);
/**
* @brief Called when the TCP socket can be written to without blocking
* @param fd File descriptor
* @param e Socket events
*/
void on_write(dpp::socket fd, const struct dpp::socket_events& e);
/**
* @brief Called when there is an error on the TCP socket
* @param fd File descriptor
* @param error_code Error code
*/
void on_error(dpp::socket fd, const struct dpp::socket_events&, int error_code);
};
}

View File

@ -0,0 +1,112 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/json_fwd.h>
#include <unordered_map>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Represents the privacy of a stage instance
*/
enum stage_privacy_level : uint8_t {
/**
* @brief The Stage instance is visible publicly, such as on Stage Discovery.
*/
sp_public = 1,
/**
* @brief The Stage instance is visible to only guild members.
*/
sp_guild_only = 2
};
/**
* @brief A stage instance.
* Stage instances are like a conference facility, with moderators/speakers and listeners.
*/
struct DPP_EXPORT stage_instance : public managed, public json_interface<stage_instance> {
protected:
friend struct json_interface<stage_instance>;
/**
* @brief Serialise a stage_instance object rom json
*
* @return stage_instance& a reference to self
*/
stage_instance& fill_from_json_impl(const nlohmann::json* j);
/**
* @brief Build json for this object
*
* @param with_id include ID
* @return json Json of this object
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief The guild ID of the associated Stage channel.
*/
snowflake guild_id;
/**
* @brief The ID of the associated Stage channel.
*/
snowflake channel_id;
/**
* @brief The topic of the Stage instance (1-120 characters).
*/
std::string topic;
/**
* @brief The privacy level of the Stage instance.
*/
stage_privacy_level privacy_level;
/**
* @brief Whether or not Stage Discovery is disabled.
*/
bool discoverable_disabled;
/**
* @brief Create a stage_instance object
*/
stage_instance();
/**
* @brief Destroy the stage_instance object
*/
~stage_instance() = default;
};
/**
* @brief A group of stage instances
*/
typedef std::unordered_map<snowflake, stage_instance> stage_instance_map;
}

223
include/dpp/stringops.h Normal file
View File

@ -0,0 +1,223 @@
/************************************************************************************
*
* D++ - A Lightweight C++ Library for Discord
*
* stringops.h taken from TriviaBot
*
* Copyright 2004 Craig Edwards <support@sporks.gg>
*
* Core based on Sporks, the Learning Discord Bot, Craig Edwards (c) 2019.
*
* 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.
*
************************************************************************************/
#pragma once
#include <string>
#include <iomanip>
#include <locale>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <charconv>
namespace dpp {
/**
* @brief Convert a string to lowercase using tolower()
*
* @tparam T type of string
* @param s String to lowercase
* @return std::basic_string<T> lowercased string
*/
template <typename T> std::basic_string<T> lowercase(const std::basic_string<T>& s)
{
std::basic_string<T> s2 = s;
std::transform(s2.begin(), s2.end(), s2.begin(), tolower);
return s2;
}
/**
* @brief Convert a string to uppercase using toupper()
*
* @tparam T type of string
* @param s String to uppercase
* @return std::basic_string<T> uppercased string
*/
template <typename T> std::basic_string<T> uppercase(const std::basic_string<T>& s)
{
std::basic_string<T> s2 = s;
std::transform(s2.begin(), s2.end(), s2.begin(), toupper);
return s2;
}
/**
* @brief trim from end of string (right)
*
* @param s String to trim
* @return std::string trimmed string
*/
inline std::string rtrim(std::string s)
{
s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1);
return s;
}
/**
* @brief trim from beginning of string (left)
*
* @param s string to trim
* @return std::string trimmed string
*/
inline std::string ltrim(std::string s)
{
s.erase(0, s.find_first_not_of(" \t\n\r\f\v"));
return s;
}
/**
* @brief Trim from both ends of string (right then left)
*
* @param s string to trim
* @return std::string trimmed string
*/
inline std::string trim(std::string s)
{
return ltrim(rtrim(s));
}
/**
* @brief Add commas to a string (or dots) based on current locale server-side
*
* @tparam T type of numeric value
* @param value Value
* @return std::string number with commas added
*/
template<class T> std::string comma(T value)
{
std::stringstream ss;
ss.imbue(std::locale(""));
ss << std::fixed << value;
return ss.str();
}
/**
* @brief Convert any value from a string to another type using stringstream.
* The optional second parameter indicates the format of the input string,
* e.g. std::dec for decimal, std::hex for hex, std::oct for octal.
*
* @tparam T Type to convert to
* @param s String to convert from
* @param f Numeric base, e.g. `std::dec` or `std::hex`
* @return T Returned numeric value
*/
template <typename T> T from_string(const std::string &s, std::ios_base & (*f)(std::ios_base&))
{
T t;
std::istringstream iss(s);
iss >> f, iss >> t;
return t;
}
/**
* @brief Convert any value from a string to another type using stringstream.
*
* @tparam T Type to convert to
* @param s String to convert from
* @return T Returned numeric value
*
* @note Base 10 for numeric conversions.
*/
template <typename T> T from_string(const std::string &s)
{
if (s.empty()) {
return static_cast<T>(0);
}
T t;
std::istringstream iss(s);
iss >> t;
return t;
}
/**
* @brief Specialised conversion of uint64_t from string
*
* @tparam int64_t
* @param s string to convert
* @return uint64_t return value
*/
template <uint64_t> uint64_t from_string(const std::string &s)
{
return std::stoull(s, 0, 10);
}
/**
* @brief Specialised conversion of uint32_t from string
*
* @tparam uint32_t
* @param s string to convert
* @return uint32_t return value
*/
template <uint32_t> uint32_t from_string(const std::string &s)
{
return (uint32_t) std::stoul(s, 0, 10);
}
/**
* @brief Specialised conversion of int from string
*
* @tparam int
* @param s string to convert
* @return int return value
*/
template <int> int from_string(const std::string &s)
{
return std::stoi(s, 0, 10);
}
/**
* @brief Convert a numeric value to hex
*
* @tparam T numeric type
* @param i numeric value
* @param leading_zeroes set to false if you don't want the leading zeroes in the output
* @return std::string value in hex, the length will be 2* the raw size of the type
*/
template <typename T> std::string to_hex(T i, bool leading_zeroes = true)
{
char str[26] = { 0 };
size_t size = sizeof(T) * 2;
std::to_chars(std::begin(str), std::end(str), i, 16);
std::string out{str};
if (leading_zeroes && out.length() < size) {
out.insert(out.begin(), size - out.length(), '0');
}
return out;
}
/**
* @brief Format a numeric type as a string with leading zeroes
*
* @tparam T numeric type
* @param i numeric value
* @param width width of type including the leading zeroes
* @return std::string resultant string with leading zeroes
*/
template <typename T> std::string leading_zeroes(T i, size_t width)
{
std::stringstream stream;
stream.imbue(std::locale::classic());
stream << std::setfill('0') << std::setw((int)width) << std::dec << i;
return stream.str();
}
}

120
include/dpp/sysdep.h Normal file
View File

@ -0,0 +1,120 @@
/*
* Discord erlpack - tidied up for D++, Craig Edwards 2021.
*
* MessagePack system dependencies modified for erlpack.
*
* Copyright (C) 2008-2010 FURUHASHI Sadayuki
*
* 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.
*/
#pragma once
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#if defined(__linux__)
#include <endian.h>
#endif
#ifdef _WIN32
#ifdef __cplusplus
/* numeric_limits<T>::min,max */
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#endif
#else
#include <arpa/inet.h> /* __BYTE_ORDER */
#endif
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__
#elif __BYTE_ORDER == __BIG_ENDIAN
#define __BIG_ENDIAN__
#elif _WIN32
#define __LITTLE_ENDIAN__
#endif
#endif
#ifdef __LITTLE_ENDIAN__
#ifdef _WIN32
# if defined(ntohs)
# define etf_byte_order_16(x) ntohs(x)
# elif defined(_byteswap_ushort) || (defined(_MSC_VER) && _MSC_VER >= 1400)
# define etf_byte_order_16(x) ((uint16_t)_byteswap_ushort((unsigned short)x))
# else
# define etf_byte_order_16(x) ( \
((((uint16_t)x) << 8) ) | \
((((uint16_t)x) >> 8) ) )
# endif
#else
# define etf_byte_order_16(x) ntohs(x)
#endif
#ifdef _WIN32
# if defined(ntohl)
# define etf_byte_order_32(x) ntohl(x)
# elif defined(_byteswap_ulong) || (defined(_MSC_VER) && _MSC_VER >= 1400)
# define etf_byte_order_32(x) ((uint32_t)_byteswap_ulong((unsigned long)x))
# else
# define etf_byte_order_32(x) \
( ((((uint32_t)x) << 24) ) | \
((((uint32_t)x) << 8) & 0x00ff0000U ) | \
((((uint32_t)x) >> 8) & 0x0000ff00U ) | \
((((uint32_t)x) >> 24) ) )
# endif
#else
# define etf_byte_order_32(x) ntohl(x)
#endif
#if defined(_byteswap_uint64) || (defined(_MSC_VER) && _MSC_VER >= 1400)
# define etf_byte_order_64(x) (_byteswap_uint64(x))
#elif defined(bswap_64)
# define etf_byte_order_64(x) bswap_64(x)
#elif defined(__DARWIN_OSSwapInt64)
# define etf_byte_order_64(x) __DARWIN_OSSwapInt64(x)
#elif defined(__linux__)
# define etf_byte_order_64(x) be64toh(x)
#else
# define etf_byte_order_64(x) \
( ((((uint64_t)x) << 56) ) | \
((((uint64_t)x) << 40) & 0x00ff000000000000ULL ) | \
((((uint64_t)x) << 24) & 0x0000ff0000000000ULL ) | \
((((uint64_t)x) << 8) & 0x000000ff00000000ULL ) | \
((((uint64_t)x) >> 8) & 0x00000000ff000000ULL ) | \
((((uint64_t)x) >> 24) & 0x0000000000ff0000ULL ) | \
((((uint64_t)x) >> 40) & 0x000000000000ff00ULL ) | \
((((uint64_t)x) >> 56) ) )
#endif
#else
#define etf_byte_order_16(x) (x)
#define etf_byte_order_32(x) (x)
#define etf_byte_order_64(x) (x)
#endif
#define store_16_bits(to, num) \
do { uint16_t val = etf_byte_order_16(num); memcpy(to, &val, 2); } while(0)
#define store_32_bits(to, num) \
do { uint32_t val = etf_byte_order_32(num); memcpy(to, &val, 4); } while(0)
#define store_64_bits(to, num) \
do { uint64_t val = etf_byte_order_64(num); memcpy(to, &val, 8); } while(0)

236
include/dpp/thread.h Normal file
View File

@ -0,0 +1,236 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/channel.h>
#include <dpp/message.h>
namespace dpp {
/**
* @brief represents membership of a user with a thread
*/
struct DPP_EXPORT thread_member : public json_interface<thread_member> {
protected:
friend struct json_interface<thread_member>;
/**
* @brief Read struct values from a json object
* @param j json to read values from
* @return A reference to self
*/
thread_member& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief ID of the thread member is part of.
*/
snowflake thread_id = {};
/**
* @brief ID of the member.
*/
snowflake user_id = {};
/**
* @brief The time when user last joined the thread.
*/
time_t joined = 0;
/**
* @brief Any user-thread settings, currently only used for notifications.
*/
uint32_t flags = 0;
};
/**
* @brief A group of thread member objects. the key is the user_id of the dpp::thread_member
*/
typedef std::unordered_map<snowflake, thread_member> thread_member_map;
/**
* @brief metadata for threads
*/
struct DPP_EXPORT thread_metadata {
/**
* @brief Timestamp when the thread's archive status was last changed, used for calculating recent activity.
*/
time_t archive_timestamp;
/**
* @brief The duration in minutes to automatically archive the thread after recent activity (60, 1440, 4320, 10080).
*/
uint16_t auto_archive_duration;
/**
* @brief Whether a thread is archived
*/
bool archived;
/**
* @brief Whether a thread is locked. When a thread is locked,
* only users with `MANAGE_THREADS` can un-archive it.
*/
bool locked;
/**
* @brief Whether non-moderators can add other non-moderators. Only for private threads.
*/
bool invitable;
};
/** @brief A definition of a discord thread.
* A thread is a superset of a channel. Not to be confused with `std::thread`!
*/
class DPP_EXPORT thread : public channel, public json_interface<thread> {
protected:
friend struct json_interface<thread>;
/** Read class values from json object
* @param j A json object to read from
* @return A reference to self
*/
thread& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build json for this thread object
*
* @param with_id include the ID in the json
* @return std::string JSON string
*/
json to_json_impl(bool with_id = false) const override;
public:
using json_interface<thread>::fill_from_json;
using json_interface<thread>::build_json;
using json_interface<thread>::to_json;
/**
* @brief Thread member of current user if joined to the thread.
* Note this is only set by certain api calls otherwise contains default data
*/
thread_member member = {};
/**
* @brief Thread metadata (threads)
*/
thread_metadata metadata = {};
/**
* @brief Created message. Only filled within the cluster::thread_create_in_forum() method
*/
message msg = {};
/**
* @brief A list of dpp::forum_tag IDs that have been applied to a thread in a forum or media channel.
*/
std::vector<snowflake> applied_tags = {};
/**
* @brief Number of messages ever sent in the thread.
* It's similar to thread::message_count on message creation, but will not decrement the number when a message is deleted
*/
uint32_t total_messages_sent = 0;
/**
* @brief Number of messages (not including the initial message or deleted messages) of the thread.
* For threads created before July 1, 2022, the message count is inaccurate when it's greater than 50.
*/
uint8_t message_count = 0;
/**
* @brief Approximate count of members in a thread (stops counting at 50)
*/
uint8_t member_count = 0;
/**
* @brief Was this thread newly created?
* @note This will only show in dpp::cluster::on_thread_create if the thread was just made.
*/
bool newly_created{false};
/**
* @brief Returns true if the thread is within an announcement channel
*
* @return true if announcement thread
*/
constexpr bool is_news_thread() const noexcept {
return (flags & channel::CHANNEL_TYPE_MASK) == CHANNEL_ANNOUNCEMENT_THREAD;
}
/**
* @brief Returns true if the channel is a public thread
*
* @return true if public thread
*/
constexpr bool is_public_thread() const noexcept {
return (flags & channel::CHANNEL_TYPE_MASK) == CHANNEL_PUBLIC_THREAD;
}
/**
* @brief Returns true if the channel is a private thread
*
* @return true if private thread
*/
constexpr bool is_private_thread() const noexcept {
return (flags & channel::CHANNEL_TYPE_MASK) == CHANNEL_PRIVATE_THREAD;
}
};
/**
* @brief Serialize a thread_metadata object to json
*
* @param j JSON object to serialize to
* @param tmdata object to serialize
*/
void to_json(nlohmann::json& j, const thread_metadata& tmdata);
/**
* @brief A group of threads
*/
typedef std::unordered_map<snowflake, thread> thread_map;
/**
* @brief A thread alongside the bot's optional thread_member object tied to it
*/
struct active_thread_info {
/**
* @brief The thread object
*/
thread active_thread;
/**
* @brief The bot as a thread member, only present if the bot is in the thread
*/
std::optional<thread_member> bot_member;
};
/**
* @brief A map of threads alongside optionally the thread_member tied to the bot if it is in the thread. The map's key is the thread id. Returned from the cluster::threads_get_active method
*/
using active_threads = std::map<snowflake, active_thread_info>;
}

117
include/dpp/thread_pool.h Normal file
View File

@ -0,0 +1,117 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <thread>
#include <queue>
#include <vector>
#include <condition_variable>
#include <mutex>
#include <functional>
namespace dpp {
/**
* @brief A work unit is a lambda executed in the thread pool
*/
using work_unit = std::function<void()>;
/**
* @brief A task within a thread pool. A simple lambda that accepts no parameters and returns void.
*/
struct DPP_EXPORT thread_pool_task {
/**
* @brief Task priority, lower value is higher priority
*/
int priority;
/**
* @brief Work unit to execute as the task
*/
work_unit function;
};
/**
* @brief Compares two thread pool tasks by priority
*/
struct DPP_EXPORT thread_pool_task_comparator {
/**
* @brief Compare two tasks
* @param a first task
* @param b second task
* @return true if a > b
*/
bool operator()(const thread_pool_task &a, const thread_pool_task &b) const {
return a.priority > b.priority;
};
};
/**
* @brief A thread pool contains 1 or more worker threads which accept thread_pool_task lambadas
* into a queue, which is processed in-order by whichever thread is free.
*/
struct DPP_EXPORT thread_pool {
/**
* @brief Threads that comprise the thread pool
*/
std::vector<std::thread> threads;
/**
* @brief Priority queue of tasks to be executed
*/
std::priority_queue<thread_pool_task, std::vector<thread_pool_task>, thread_pool_task_comparator> tasks;
/**
* @brief Mutex for accessing the priority queue
*/
std::mutex queue_mutex;
/**
* @brief Condition variable to notify for new tasks to run
*/
std::condition_variable cv;
/**
* @brief True if the thread pool is due to stop
*/
bool stop{false};
/**
* @brief Create a new priority thread pool
* @param creator creating cluster (for logging)
* @param num_threads number of threads in the pool
*/
explicit thread_pool(class cluster* creator, size_t num_threads = std::thread::hardware_concurrency());
/**
* @brief Destroy the thread pool
*/
~thread_pool();
/**
* @brief Enqueue a new task to the thread pool
* @param task task to enqueue
*/
void enqueue(thread_pool_task task);
};
}

View File

@ -0,0 +1,105 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/cluster.h>
#include <time.h>
#include <map>
#include <functional>
#include <string>
namespace dpp {
/**
* @brief A timed_listener is a way to temporarily attach to an event for a specific timeframe, then detach when complete.
* A lambda may also be optionally called when the timeout is reached. Destructing the timed_listener detaches any attached
* event listeners, and cancels any created timers, but does not call any timeout lambda.
*
* @tparam attached_event Event within cluster to attach to within the cluster::dispatch member (dpp::dispatcher object)
* @tparam listening_function Definition of lambda function that matches up with the attached_event.
*/
template <typename attached_event, class listening_function> class timed_listener
{
private:
/**
* @brief Owning cluster.
*/
cluster* owner;
/**
* @brief Duration of listen.
*/
time_t duration;
/**
* @brief Reference to attached event in cluster.
*/
//event_router_t<thread_member_update_t> on_thread_member_update;
attached_event& ev;
/**
* @brief Timer handle.
*/
timer th;
/**
* @brief Event handle.
*/
event_handle listener_handle;
public:
/**
* @brief Construct a new timed listener object
*
* @param cl Owning cluster
* @param _duration Duration of timed event in seconds
* @param event Event to hook, e.g. cluster.on_message_create
* @param on_end An optional void() lambda to trigger when the timed_listener times out.
* Calling the destructor before the timeout is reached does not call this lambda.
* @param listener Lambda to receive events. Type must match up properly with that passed into the 'event' parameter.
*/
timed_listener(cluster* cl, uint64_t _duration, attached_event& event, listening_function listener, timer_callback_t on_end = {})
: owner(cl), duration(_duration), ev(event)
{
/* Attach event */
listener_handle = ev(listener);
/* Create timer */
th = cl->start_timer([this]([[maybe_unused]] dpp::timer timer_handle) {
/* Timer has finished, detach it from event.
* Only allowed to tick once.
*/
ev.detach(listener_handle);
owner->stop_timer(th);
}, duration, on_end);
}
/**
* @brief Destroy the timed listener object
*/
~timed_listener() {
/* Stop timer and detach event, but do not call on_end */
ev.detach(listener_handle);
owner->stop_timer(th);
}
};
}

150
include/dpp/timer.h Normal file
View File

@ -0,0 +1,150 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <cstdint>
#include <map>
#include <unordered_map>
#include <cstddef>
#include <ctime>
#include <set>
#include <queue>
#include <functional>
namespace dpp {
/**
* @brief Represents a timer handle.
* Returned from cluster::start_timer and used by cluster::stop_timer.
* This is obtained from a simple incrementing value, internally.
*/
typedef size_t timer;
/**
* @brief The type for a timer callback
*/
typedef std::function<void(timer)> timer_callback_t;
/**
* @brief Used internally to store state of active timers
*/
struct DPP_EXPORT timer_t {
/**
* @brief Timer handle
*/
timer handle{0};
/**
* @brief Next timer tick as unix epoch
*/
time_t next_tick{0};
/**
* @brief Frequency between ticks
*/
uint64_t frequency{0};
/**
* @brief Lambda to call on tick
*/
timer_callback_t on_tick{};
/**
* @brief Lambda to call on stop (optional)
*/
timer_callback_t on_stop{};
};
/**
* @brief Used to compare two timers next tick times in a priority queue
*/
struct DPP_EXPORT timer_comparator {
/**
* @brief Compare two timers
* @param a first timer
* @param b second timer
* @return returns true if a > b
*/
bool operator()(const timer_t &a, const timer_t &b) const {
return a.next_tick > b.next_tick;
};
};
/**
* @brief A priority timers, ordered by earliest first so that the head is always the
* soonest to be due.
*/
typedef std::priority_queue<timer_t, std::vector<timer_t>, timer_comparator> timer_next_t;
/**
* @brief A set of deleted timer handles
*/
typedef std::set<timer> timers_deleted_t;
/**
* @brief Trigger a timed event once.
* The provided callback is called only once.
*/
class DPP_EXPORT oneshot_timer
{
private:
/**
* @brief Owning cluster.
*/
class cluster* owner;
/**
* @brief Timer handle.
*/
timer th;
public:
/**
* @brief Construct a new oneshot timer object
*
* @param cl cluster owner
* @param duration duration before firing
* @param callback callback to call on firing
*/
oneshot_timer(class cluster* cl, uint64_t duration, timer_callback_t callback);
/**
* @brief Get the handle for the created one-shot timer
*
* @return timer handle for use with stop_timer
*/
timer get_handle();
/**
* @brief Cancel the one shot timer immediately.
* Callback function is not called.
*/
void cancel();
/**
* @brief Destroy the oneshot timer object
*/
~oneshot_timer();
};
}

5739
include/dpp/unicode_emoji.h Normal file

File diff suppressed because it is too large Load Diff

584
include/dpp/user.h Normal file
View File

@ -0,0 +1,584 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/json_fwd.h>
#include <dpp/snowflake.h>
#include <dpp/managed.h>
#include <dpp/utility.h>
#include <dpp/json_interface.h>
namespace dpp {
constexpr uint32_t MAX_AVATAR_SIZE = 10240 * 1000; // 10240KB.
/**
* @brief Various bitmask flags used to represent information about a dpp::user
*/
enum user_flags : uint32_t {
/**
* @brief User is a bot.
*/
u_bot = 0b00000000000000000000000000000001,
/**
* @brief User is a system user (Clyde!).
*/
u_system = 0b00000000000000000000000000000010,
/**
* @brief User has multi-factor authentication enabled.
*/
u_mfa_enabled = 0b00000000000000000000000000000100,
/**
* @brief User is verified (verified email address).
*/
u_verified = 0b00000000000000000000000000001000,
/**
* @brief User has full nitro.
*/
u_nitro_full = 0b00000000000000000000000000010000,
/**
* @brief User has nitro classic.
*/
u_nitro_classic = 0b00000000000000000000000000100000,
/**
* @brief User is discord staff.
*/
u_discord_employee = 0b00000000000000000000000001000000,
/**
* @brief User owns a partnered server.
*/
u_partnered_owner = 0b00000000000000000000000010000000,
/**
* @brief User is a member of hypesquad events.
*/
u_hypesquad_events = 0b00000000000000000000000100000000,
/**
* @brief User has BugHunter level 1.
*/
u_bughunter_1 = 0b00000000000000000000001000000000,
/**
* @brief User is a member of House Bravery.
*/
u_house_bravery = 0b00000000000000000000010000000000,
/**
* @brief User is a member of House Brilliance.
*/
u_house_brilliance = 0b00000000000000000000100000000000,
/**
* @brief User is a member of House Balance.
*/
u_house_balance = 0b00000000000000000001000000000000,
/**
* @brief User is an early supporter.
*/
u_early_supporter = 0b00000000000000000010000000000000,
/**
* @brief User is a team user.
*/
u_team_user = 0b00000000000000000100000000000000,
/**
* @brief User is has Bug Hunter level 2.
*/
u_bughunter_2 = 0b00000000000000001000000000000000,
/**
* @brief User is a verified bot.
*/
u_verified_bot = 0b00000000000000010000000000000000,
/**
* @brief User has the Early Verified Bot Developer badge.
*/
u_verified_bot_dev = 0b00000000000000100000000000000000,
/**
* @brief User's icon is animated.
*/
u_animated_icon = 0b00000000000001000000000000000000,
/**
* @brief User is a certified moderator.
*/
u_certified_moderator = 0b00000000000010000000000000000000,
/**
* @brief User is a bot using HTTP interactions.
*
* @note shows online even when not connected to a websocket.
*/
u_bot_http_interactions = 0b00000000000100000000000000000000,
/**
* @brief User has nitro basic.
*/
u_nitro_basic = 0b00000000001000000000000000000000,
/**
* @brief User has the active developer badge.
*/
u_active_developer = 0b00000000010000000000000000000000,
/**
* @brief User's banner is animated.
*/
u_animated_banner = 0b00000000100000000000000000000000,
};
/**
* @brief Represents a user on discord. May or may not be a member of a dpp::guild.
*/
class DPP_EXPORT user : public managed, public json_interface<user> {
protected:
friend struct json_interface<user>;
/** Fill this record from json.
* @param j The json to fill this record from
* @return Reference to self
*/
user& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Convert to JSON
*
* @param with_id include ID in output
* @return json JSON output
*/
virtual json to_json_impl(bool with_id = true) const;
public:
/**
* @brief Discord username.
*/
std::string username;
/**
* @brief Global display name.
*/
std::string global_name;
/**
* @brief Avatar hash.
*/
utility::iconhash avatar;
/**
* @brief Avatar decoration hash.
*/
utility::iconhash avatar_decoration;
/**
* @brief Flags built from a bitmask of values in dpp::user_flags.
*/
uint32_t flags;
/**
* @brief Discriminator (aka tag), 4 digits usually displayed with leading zeroes.
*
* @note To print the discriminator with leading zeroes, use format_username().
* 0 for users that have migrated to the new username format.
*/
uint16_t discriminator;
/**
* @brief Reference count of how many guilds this user is in.
*/
uint8_t refcount;
/**
* @brief Construct a new user object
*/
user();
/**
* @brief Destroy the user object
*/
virtual ~user() = default;
/**
* @brief Create a mentionable user.
* @param id The ID of the user.
* @return std::string The formatted mention of the user.
*/
static std::string get_mention(const snowflake& id);
/**
* @brief Get the avatar url of the user
*
* @note If the user doesn't have an avatar, the default user avatar url is returned which is always in `png` format!
*
* @param size The size of the avatar in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized avatar is returned.
* @param format The format to use for the avatar. It can be one of `i_webp`, `i_jpg`, `i_png` or `i_gif`.
* When passing `i_gif`, it returns an empty string for non-animated images. Consider using the `prefer_animated` parameter instead.
* @param prefer_animated Whether you prefer gif format.
* If true, it'll return gif format whenever the image is available as animated.
* @return std::string avatar url or an empty string, if required attributes are missing or an invalid format was passed
*/
std::string get_avatar_url(uint16_t size = 0, const image_type format = i_png, bool prefer_animated = true) const;
/**
* @brief Get the default avatar url of the user. This is calculated by the discriminator.
*
* @return std::string avatar url or an empty string, if the discriminator is empty
*/
std::string get_default_avatar_url() const;
/**
* @brief Get the avatar decoration url of the user if they have one, otherwise returns an empty string.
*
* @param size The size of the avatar decoration in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized avatar decoration is returned.
* @return std::string avatar url or an empty string
*/
std::string get_avatar_decoration_url(uint16_t size = 0) const;
/**
* @brief Return a ping/mention for the user
*
* @return std::string mention
*/
std::string get_mention() const;
/**
* @brief Returns URL to user
*
* @return string of URL to user
*/
std::string get_url() const;
/**
* @brief Return true if user has the active Developer badge
*
* @return true if has active developer
*/
bool is_active_developer() const;
/**
* @brief User is a bot
*
* @return True if the user is a bot
*/
bool is_bot() const;
/**
* @brief User is a system user (Clyde)
*
* @return true if user is a system user
*/
bool is_system() const;
/**
* @brief User has multi-factor authentication enabled
*
* @return true if multi-factor is enabled
*/
bool is_mfa_enabled() const;
/**
* @brief Return true if user has verified account
*
* @return true if verified
*/
bool is_verified() const;
/**
* @brief Return true if user has full nitro.
* This is mutually exclusive with full nitro.
*
* @return true if user has full nitro
*/
bool has_nitro_full() const;
/**
* @brief Return true if user has nitro classic.
* This is mutually exclusive with nitro classic.
*
* @return true if user has nitro classic
*/
bool has_nitro_classic() const;
/**
* @brief Return true if user has nitro basic.
* This is mutually exclusive with nitro basic.
*
* @return true if user has nitro basic
*/
bool has_nitro_basic() const;
/**
* @brief Return true if user is a discord employee
*
* @return true if user is discord staff
*/
bool is_discord_employee() const;
/**
* @brief Return true if user owns a partnered server
*
* @return true if user has partnered server
*/
bool is_partnered_owner() const;
/**
* @brief Return true if user has hypesquad events
*
* @return true if has hypesquad events
*/
bool has_hypesquad_events() const;
/**
* @brief Return true if user has the bughunter level 1 badge
*
* @return true if has bughunter level 1
*/
bool is_bughunter_1() const;
/**
* @brief Return true if user is in house bravery
*
* @return true if in house bravery
*/
bool is_house_bravery() const;
/**
* @brief Return true if user is in house brilliance
*
* @return true if in house brilliance
*/
bool is_house_brilliance() const;
/**
* @brief Return true if user is in house balance
*
* @return true if in house brilliance
*/
bool is_house_balance() const;
/**
* @brief Return true if user is an early supporter
*
* @return true if early supporter
*/
bool is_early_supporter() const;
/**
* @brief Return true if user is a team user
*
* @return true if a team user
*/
bool is_team_user() const;
/**
* @brief Return true if user has the bughunter level 2 badge
*
* @return true if has bughunter level 2
*/
bool is_bughunter_2() const;
/**
* @brief Return true if user has the verified bot badge
*
* @return true if verified bot
*/
bool is_verified_bot() const;
/**
* @brief Return true if user is an early verified bot developer
*
* @return true if verified bot developer
*/
bool is_verified_bot_dev() const;
/**
* @brief Return true if user is a certified moderator
*
* @return true if certified moderator
*/
bool is_certified_moderator() const;
/**
* @brief Return true if user is a bot which exclusively uses HTTP interactions.
* Bots using HTTP interactions are always considered online even when not connected to a websocket.
*
* @return true if is a http interactions only bot
*/
bool is_bot_http_interactions() const;
/**
* @brief Return true if user has an animated icon
*
* @return true if icon is animated (gif)
*/
bool has_animated_icon() const;
/**
* @brief Format a username into user\#discriminator
*
* For example Brain#0001
*
* @note This will, most often, return something like Brain#0000 due to discriminators slowly being removed.
* Some accounts, along with most bots, still have discriminators, so they will still show as Bot#1234.
*
* @return Formatted username and discriminator
*/
std::string format_username() const;
};
/**
* @brief A user with additional fields only available via the oauth2 identify scope.
* These are not included in dpp::user as additional scopes are needed to fetch them
* which bots do not normally have.
*/
class DPP_EXPORT user_identified : public user, public json_interface<user_identified> {
protected:
friend struct json_interface<user_identified>;
/** Fill this record from json.
* @param j The json to fill this record from
* @return Reference to self
*/
user_identified& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Convert to JSON
*
* @param with_id include ID in output
* @return json JSON output
*/
virtual json to_json_impl(bool with_id = true) const;
public:
/**
* @brief Optional: The user's chosen language option identify.
*/
std::string locale;
/**
* @brief Optional: The user's email.
*
* @note This may be empty.
*/
std::string email;
/**
* @brief Optional: The user's banner hash identify.
*
* @note This may be empty.
*/
utility::iconhash banner;
/**
* @brief Optional: The user's banner color encoded as an integer representation of hexadecimal color code identify.
*
* @note This may be empty.
*/
uint32_t accent_color;
/**
* @brief Optional: Whether the email on this account has been verified email.
*/
bool verified;
/**
* @brief Construct a new user identified object
*/
user_identified();
/**
* @brief Construct a new user identified object from a user object
*
* @param u user object
*/
user_identified(const user& u);
/**
* @brief Destroy the user identified object
*/
virtual ~user_identified() = default;
using json_interface<user_identified>::fill_from_json;
using json_interface<user_identified>::build_json;
using json_interface<user_identified>::to_json;
/**
* @brief Return true if user has an animated banner
*
* @return true if banner is animated (gif)
*/
bool has_animated_banner() const;
/**
* @brief Get the user identified's banner url if they have one, otherwise returns an empty string
*
* @param size The size of the banner in pixels. It can be any power of two between 16 and 4096,
* otherwise the default sized banner is returned.
* @param format The format to use for the avatar. It can be one of `i_webp`, `i_jpg`, `i_png` or `i_gif`.
* When passing `i_gif`, it returns an empty string for non-animated images. Consider using the `prefer_animated` parameter instead.
* @param prefer_animated Whether you prefer gif format.
* If true, it'll return gif format whenever the image is available as animated.
* @return std::string banner url or an empty string, if required attributes are missing or an invalid format was passed
*/
std::string get_banner_url(uint16_t size = 0, const image_type format = i_png, bool prefer_animated = true) const;
};
/**
* @brief helper function to deserialize a user from json
*
* @see https://github.com/nlohmann/json#arbitrary-types-conversions
*
* @param j output json object
* @param u user to be deserialized
*/
void from_json(const nlohmann::json& j, user& u);
/**
* @brief helper function to deserialize a user_identified from json
*
* @see https://github.com/nlohmann/json#arbitrary-types-conversions
*
* @param j output json object
* @param u user to be deserialized
*/
void from_json(const nlohmann::json& j, user_identified& u);
/**
* @brief A group of users.
*/
typedef std::unordered_map<snowflake, user> user_map;
}

1069
include/dpp/utility.h Normal file

File diff suppressed because it is too large Load Diff

32
include/dpp/version.h Normal file
View File

@ -0,0 +1,32 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#ifndef DPP_VERSION_LONG
#define DPP_VERSION_LONG 0x00100104
#define DPP_VERSION_SHORT 100104
#define DPP_VERSION_TEXT "D++ 10.1.4 (07-Jul-2025)"
#define DPP_VERSION_MAJOR ((DPP_VERSION_LONG & 0x00ff0000) >> 16)
#define DPP_VERSION_MINOR ((DPP_VERSION_LONG & 0x0000ff00) >> 8)
#define DPP_VERSION_PATCH (DPP_VERSION_LONG & 0x000000ff)
#endif

126
include/dpp/voiceregion.h Normal file
View File

@ -0,0 +1,126 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <unordered_map>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Flags related to a voice region
*/
enum voiceregion_flags {
/**
* @brief The closest (optimal) voice region.
*/
v_optimal = 0x00000001,
/**
* @brief A Deprecated voice region (avoid switching to these).
*/
v_deprecated = 0x00000010,
/**
* @brief A custom voice region (used for events/etc).
*/
v_custom = 0x00000100
};
/**
* @brief Represents a voice region on discord
*/
class DPP_EXPORT voiceregion : public json_interface<voiceregion> {
protected:
friend struct json_interface<voiceregion>;
/**
* @brief Fill object properties from JSON
*
* @param j JSON to fill from
* @return voiceregion& Reference to self
*/
voiceregion& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build a json for this object
*
* @param with_id Add ID to output
* @return json JSON string
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Voice server ID
*/
std::string id;
/**
* @brief Voice server name
*/
std::string name;
/**
* @brief Flags bitmap
*/
uint8_t flags;
/**
* @brief Construct a new voiceregion object
*/
voiceregion();
/**
* @brief Destroy the voiceregion object
*/
virtual ~voiceregion() = default;
/**
* @brief True if is the optimal voice server
*
* @return true if optimal
*/
bool is_optimal() const;
/**
* @brief True if is a deprecated voice server
*
* @return true if deprecated
*/
bool is_deprecated() const;
/**
* @brief True if is a custom voice server
*
* @return true if custom
*/
bool is_custom() const;
};
/**
* @brief A group of voice regions
*/
typedef std::unordered_map<std::string, voiceregion> voiceregion_map;
}

179
include/dpp/voicestate.h Normal file
View File

@ -0,0 +1,179 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/json_fwd.h>
#include <unordered_map>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Bit mask flags relating to voice states
*/
enum voicestate_flags {
/**
* @brief Deafened by the server.
*/
vs_deaf = 0b00000001,
/**
* @brief Muted by the server.
*/
vs_mute = 0b00000010,
/**
* @brief Locally Muted.
*/
vs_self_mute = 0b00000100,
/**
* @brief Locally deafened.
*/
vs_self_deaf = 0b00001000,
/**
* @brief Whether this user is streaming using "Go Live".
*/
vs_self_stream = 0b00010000,
/**
* @brief Whether this user's camera is enabled.
*/
vs_self_video = 0b00100000,
/**
* @brief Whether this user's permission to speak is denied.
*/
vs_suppress = 0b01000000
};
/**
* @brief Represents the voice state of a user on a guild
* These are stored in the dpp::guild object, and accessible there,
* or via dpp::channel::get_voice_members
*/
class DPP_EXPORT voicestate : public json_interface<voicestate> {
protected:
friend struct json_interface<voicestate>;
/**
* @brief Fill voicestate object from json data
*
* @param j JSON data to fill from
* @return voicestate& Reference to self
*/
voicestate& fill_from_json_impl(nlohmann::json* j);
public:
/**
* @brief Owning shard.
*/
int32_t shard_id{0};
/**
* @brief Optional: The guild id this voice state is for.
*/
snowflake guild_id{0};
/**
* @brief The channel id this user is connected to.
*
* @note This may be empty.
*/
snowflake channel_id{0};
/**
* @brief The user id this voice state is for.
*/
snowflake user_id{0};
/**
* @brief The session id for this voice state.
*/
std::string session_id;
/**
* @brief Voice state flags from dpp::voicestate_flags.
*/
uint8_t flags{0};
/**
* @brief The time at which the user requested to speak.
*
* @note If the user never requested to speak, this is 0.
*/
time_t request_to_speak{0};
/**
* @brief Construct a new voicestate object
*/
voicestate();
/**
* @brief Destroy the voicestate object
*/
virtual ~voicestate() = default;
/**
* @brief Return true if the user is deafened by the server.
*/
bool is_deaf() const;
/**
* @brief Return true if the user is muted by the server.
*/
bool is_mute() const;
/**
* @brief Return true if user muted themselves.
*/
bool is_self_mute() const;
/**
* @brief Return true if user deafened themselves.
*/
bool is_self_deaf() const;
/**
* @brief Return true if the user is streaming using "Go Live".
*/
bool self_stream() const;
/**
* @brief Return true if the user's camera is enabled.
*/
bool self_video() const;
/**
* @brief Return true if user is suppressed.
*
* "HELP HELP I'M BEING SUPPRESSED!"
*/
bool is_suppressed() const;
};
/** A container of voicestates */
typedef std::unordered_map<std::string, voicestate> voicestate_map;
}

208
include/dpp/webhook.h Normal file
View File

@ -0,0 +1,208 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/misc-enum.h>
#include <dpp/managed.h>
#include <dpp/json_fwd.h>
#include <dpp/user.h>
#include <dpp/guild.h>
#include <dpp/channel.h>
#include <unordered_map>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Defines types of webhook
*/
enum webhook_type {
/**
* @brief Incoming webhook.
*/
w_incoming = 1,
/**
* @brief Channel following webhook.
*/
w_channel_follower = 2,
/**
* @brief Application webhooks for interactions.
*/
w_application = 3
};
/**
* @brief Represents a discord webhook
*/
class DPP_EXPORT webhook : public managed, public json_interface<webhook> {
protected:
friend struct json_interface<webhook>;
/**
* @brief Fill in object from json data
*
* @param j JSON data
* @return webhook& Reference to self
*/
webhook& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build JSON string from object
*
* @param with_id Include the ID of the webhook in the json
* @return std::string JSON encoded object
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Type of the webhook from dpp::webhook_type.
*/
uint8_t type;
/**
* @brief The guild id this webhook is for.
*
* @note This field is optional, and may also be empty.
*/
snowflake guild_id;
/**
* @brief The channel id this webhook is for.
*
* @note This may be empty.
*/
snowflake channel_id;
/**
* @brief The user this webhook was created by.
*
* @note This field is optional.
* @warning This is not returned when getting a webhook with its token!
*/
user user_obj;
/**
* @brief The default name of the webhook.
*
* @note This may be empty.
*/
std::string name;
/**
* @brief The default avatar of the webhook.
*
* @note This value will not have any effect when `avatar_url` is set, they are mutually exclusive.
* @note This may be empty.
*/
utility::iconhash avatar;
/**
* @brief Avatar URL to use instead of the default if it is set.
*
* @note This will override `avatar` if it is set, they are mutually exclusive.
* @note This may be empty.
*/
std::string avatar_url;
/**
* @brief The secure token of the webhook (returned for Incoming Webhooks).
*
* @note This field is optional.
*/
std::string token;
/**
* @brief The bot/OAuth2 application that created this webhook.
*
* @note This may be empty.
*/
snowflake application_id;
/**
* @brief The guild of the channel that this webhook is following (only for Channel Follower Webhooks).
*
* @warning This will be absent if the webhook creator has since lost access to the guild where the followed channel resides!
*/
guild source_guild;
/**
* @brief The channel that this webhook is following (only for Channel Follower Webhooks).
*
* @warning This will be absent if the webhook creator has since lost access to the guild where the followed channel resides!
*/
channel source_channel;
/**
* @brief The url used for executing the webhook (returned by the webhooks OAuth2 flow).
*/
std::string url;
/**
* @brief base64 encoded image data if uploading a new image.
*
* @warning You should only ever read data from here. If you want to set the data, use dpp::webhook::load_image.
*/
std::string image_data;
/**
* @brief Construct a new webhook object
*/
webhook();
/**
* @brief Construct a new webhook object using the Webhook URL provided by Discord
*
* @param webhook_url a fully qualified web address of an existing webhook
* @throw logic_exception if the webhook url could not be parsed
*/
webhook(const std::string& webhook_url);
/**
* @brief Construct a new webhook object using the webhook ID and the webhook token
*
* @param webhook_id id taken from a link of an existing webhook
* @param webhook_token token taken from a link of an existing webhook
*/
webhook(const snowflake webhook_id, const std::string& webhook_token);
/**
* @brief Base64 encode image data and allocate it to image_data
*
* @param image_blob Binary image data
* @param type Image type. It can be one of `i_gif`, `i_jpg` or `i_png`.
* @param is_base64_encoded True if the image data is already base64 encoded
* @return webhook& Reference to self
* @throw dpp::length_exception Image data is larger than the maximum size of 256 kilobytes
*/
webhook& load_image(const std::string &image_blob, const image_type type, bool is_base64_encoded = false);
};
/**
* @brief A group of webhooks
*/
typedef std::unordered_map<snowflake, webhook> webhook_map;
}

View File

@ -0,0 +1,33 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
/* This file contains pragmas to disable warnings on win32 builds with msvc only.
* It is only included during build of D++ itself, and not when including the headers
* into a user's project.
*
* Before adding a warning here please be ABSOLUTELY SURE it is one we cannot easily fix
* and is to be silenced, thrown into the sarlacc pit to be eaten for 1000 years...
*/
_Pragma("warning( disable : 4251 )"); // 4251 warns when we export classes or structures with stl member variables
_Pragma("warning( disable : 5105 )"); // 5105 is to do with macro warnings

View File

@ -0,0 +1,106 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#include <dpp/exception.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <string>
#pragma once
namespace dpp::detail {
/**
* @brief This class wraps a raw SSL_CTX pointer, managing moving,
* creation, and RAII destruction.
*/
struct wrapped_ssl_ctx {
/**
* @brief SSL_CTX pointer, raw C pointer nastiness
*/
SSL_CTX *context{nullptr};
/**
* @brief Get last SSL error message
* @return SSL error message
*/
std::string get_ssl_error() {
unsigned long error_code = ERR_get_error();
if (error_code == 0) {
return "No error";
}
char error_buffer[1024]{0};
ERR_error_string_n(error_code, error_buffer, sizeof(error_buffer));
return std::string(error_buffer);
}
/**
* @brief Create a wrapped SSL context
* @param is_server true to create a server context, false to create a client context
* @throws dpp::connection_exception if context could not be created
*/
explicit wrapped_ssl_ctx(bool is_server = false) : context(SSL_CTX_new(is_server ? TLS_server_method() : TLS_client_method())) {
if (context == nullptr) {
throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context: " + get_ssl_error());
}
}
/**
* @brief Copy constructor
* @note Intentionally deleted
*/
wrapped_ssl_ctx(const wrapped_ssl_ctx&) = delete;
/**
* @brief Copy assignment operator
* @note Intentionally deleted
*/
wrapped_ssl_ctx& operator=(const wrapped_ssl_ctx&) = delete;
/**
* @brief Move constructor
* @param other source context
*/
wrapped_ssl_ctx(wrapped_ssl_ctx&& other) noexcept : context(other.context) {
other.context = nullptr;
}
/**
* @brief Move assignment operator
* @param other source context
* @return self
*/
wrapped_ssl_ctx& operator=(wrapped_ssl_ctx&& other) noexcept {
if (this != &other) {
/* Free current context if any and transfer ownership */
SSL_CTX_free(context);
context = other.context;
other.context = nullptr;
}
return *this;
}
~wrapped_ssl_ctx() {
SSL_CTX_free(context);
}
};
};

252
include/dpp/wsclient.h Normal file
View File

@ -0,0 +1,252 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <map>
#include <dpp/sslconnection.h>
namespace dpp {
/**
* @brief Websocket protocol types available on Discord
*/
enum websocket_protocol_t : uint8_t {
/**
* @brief JSON data, text, UTF-8 character set
*/
ws_json = 0,
/**
* @brief Erlang Term Format (ETF) binary protocol
*/
ws_etf = 1
};
/**
* @brief Websocket connection status
*/
enum ws_state : uint8_t {
/**
* @brief Sending/receiving HTTP headers, acting as a standard HTTP connection.
* This is the state prior to receiving "HTTP/1.1 101 Switching Protocols" from the
* server side.
*/
HTTP_HEADERS,
/**
* @brief Connected as a websocket, and "upgraded". Now talking using binary frames.
*/
CONNECTED
};
/**
* @brief Low-level websocket opcodes for frames
*/
enum ws_opcode : uint8_t {
/**
* @brief Continuation.
*/
OP_CONTINUATION = 0x00,
/**
* @brief Text frame.
*/
OP_TEXT = 0x01,
/**
* @brief Binary frame.
*/
OP_BINARY = 0x02,
/**
* @brief Close notification with close code.
*/
OP_CLOSE = 0x08,
/**
* @brief Low level ping.
*/
OP_PING = 0x09,
/**
* @brief Low level pong.
*/
OP_PONG = 0x0a,
/**
* @brief Automatic selection of type
*/
OP_AUTO = 0xff,
};
/**
* @brief Implements a websocket client based on the SSL client
*/
class DPP_EXPORT websocket_client : public ssl_connection {
/**
* @brief Connection key used in the HTTP headers
*/
std::string key;
/**
* @brief Current websocket state
*/
ws_state state;
/**
* @brief Path part of URL for websocket
*/
std::string path;
/**
* @brief Data opcode, represents the type of frames we send
*/
ws_opcode data_opcode;
/**
* @brief HTTP headers received on connecting/upgrading
*/
std::map<std::string, std::string> http_headers;
/**
* @brief Parse headers for a websocket frame from the buffer.
* @param buffer The buffer to operate on. Will modify the string removing completed items from the head of the queue
* @return true if a complete header has been received
*/
bool parseheader(std::string& buffer);
/**
* @brief Fill a header for outbound messages
* @param outbuf The raw frame to fill
* @param sendlength The size of the data to encapsulate
* @param opcode the ws_opcode to send in the header
* @return size of filled header
*/
size_t fill_header(unsigned char* outbuf, size_t sendlength, ws_opcode opcode);
/**
* @brief Handle ping requests.
* @param payload The ping payload, to be returned as-is for a pong
*/
void handle_ping(const std::string& payload);
protected:
/**
* @brief Connect to websocket server
*/
virtual void connect() override;
/**
* @brief Get websocket state
* @return websocket state
*/
[[nodiscard]] ws_state get_state() const;
/**
* @brief If true the connection timed out while waiting,
* when waiting for SSL negotiation, TCP connect(), or HTTP.
*/
bool timed_out;
/**
* @brief Time at which the connection should be abandoned,
* if we are still connecting or negotiating with a HTTP server
*/
time_t timeout;
public:
/**
* @brief Connect to a specific websocket server.
* @param creator Creating cluster
* @param hostname Hostname to connect to
* @param port Port to connect to
* @param urlpath The URL path components of the HTTP request to send
* @param opcode The encoding type to use, either OP_BINARY or OP_TEXT
* @note This just indicates the default for frames sent. Certain sockets,
* such as voice websockets, may send a combination of OP_TEXT and OP_BINARY
* frames, whereas shard websockets will only ever send OP_BINARY for ETF and
* OP_TEXT for JSON.
*/
websocket_client(cluster* creator, const std::string& hostname, const std::string& port = "443", const std::string& urlpath = "", ws_opcode opcode = OP_BINARY);
/**
* @brief Destroy the websocket client object
*/
virtual ~websocket_client() = default;
/**
* @brief Write to websocket. Encapsulates data in frames if the status is CONNECTED.
* @param data The data to send.
* @param _opcode The opcode of the data to send, either binary or text. The default
* is to use the socket's opcode as set in the constructor.
*/
virtual void write(const std::string_view data, ws_opcode _opcode = OP_AUTO);
/**
* @brief Processes incoming frames from the SSL socket input buffer.
* @param buffer The buffer contents. Can modify this value removing the head elements when processed.
*/
virtual bool handle_buffer(std::string& buffer) override;
/**
* @brief Close websocket
*/
virtual void close() override;
/**
* @brief Receives raw frame content only without headers
*
* @param buffer The buffer contents
* @param opcode Frame type, e.g. OP_TEXT, OP_BINARY
* @return True if the frame was successfully handled. False if no valid frame is in the buffer.
*/
virtual bool handle_frame(const std::string& buffer, ws_opcode opcode);
/**
* @brief Called upon error frame.
*
* @param errorcode The error code from the websocket server
*/
virtual void error(uint32_t errorcode);
/**
* @brief Fires every second from the underlying socket I/O loop, used for sending websocket pings
*/
virtual void one_second_timer() override;
/**
* @brief Send OP_CLOSE error code 1000 to the other side of the connection.
* This indicates graceful close.
* @note This informs Discord to invalidate the session, you cannot resume if you send this
*/
void send_close_packet();
/**
* @brief Called on HTTP socket closure
*/
virtual void on_disconnect();
};
}

85
include/dpp/zlibcontext.h Normal file
View File

@ -0,0 +1,85 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/exception.h>
#include <cstdint>
#include <vector>
#include <memory>
/**
* @brief Forward declaration for zlib stream type
*/
typedef struct z_stream_s z_stream;
namespace dpp {
/**
* @brief Size of decompression buffer for zlib compressed traffic
*/
constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024;
/**
* @brief This is an opaque class containing zlib library specific structures.
* This wraps the C pointers needed for zlib with unique_ptr and gives us a nice
* buffer abstraction so we don't need to wrestle with raw pointers.
*/
class DPP_EXPORT zlibcontext {
public:
/**
* @brief Zlib stream struct. The actual type is defined in zlib.h
* so is only defined in the implementation file.
*/
z_stream* d_stream{};
/**
* @brief ZLib decompression buffer.
* This is automatically set to DECOMP_BUFFER_SIZE bytes when
* the class is constructed.
*/
std::vector<unsigned char> decomp_buffer{};
/**
* @brief Total decompressed received bytes counter
*/
uint64_t decompressed_total{};
/**
* @brief Initialise zlib struct via inflateInit()
* and size the buffer
*/
zlibcontext();
/**
* @brief Destroy zlib struct via inflateEnd()
*/
~zlibcontext();
/**
* @brief Decompress zlib deflated buffer
* @param buffer input compressed stream
* @param decompressed output decompressed content
* @return an error code on error, or err_no_code_specified (0) on success
*/
exception_error_code decompress(const std::string& buffer, std::string& decompressed);
};
}

1
include/stb Submodule

@ -0,0 +1 @@
Subproject commit f58f558c120e9b32c217290b80bad1a0729fbb2c

1
lib/libdpp.so Symbolic link
View File

@ -0,0 +1 @@
libdpp.so.10.1.4

Some files were not shown because too many files have changed in this diff Show More