flatten heiharchy

This commit is contained in:
jason 2025-08-14 15:41:56 +00:00
parent 8d52121066
commit 134df8a2ec
95 changed files with 80284 additions and 0 deletions

1669
dpp/appcommand.h Normal file

File diff suppressed because it is too large Load Diff

523
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
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
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
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
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
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
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
dpp/cluster.h Normal file

File diff suppressed because it is too large Load Diff

2652
dpp/cluster_coro_calls.h Normal file

File diff suppressed because it is too large Load Diff

473
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
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

428
dpp/commandhandler.h Normal file
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
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
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 */

735
dpp/coro/awaitable.h Normal file
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
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 */

406
dpp/coro/coroutine.h Normal file
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
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
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
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
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
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);
}

1267
dpp/discordvoiceclient.h Normal file

File diff suppressed because it is too large Load Diff

2319
dpp/dispatcher.h Normal file

File diff suppressed because it is too large Load Diff

98
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
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
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
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
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
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
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
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
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
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
dpp/guild.h Normal file

File diff suppressed because it is too large Load Diff

74
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);
}
};
}

260
dpp/http_server_request.h Normal file
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
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
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
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
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
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
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
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

79
dpp/isa/fallback.h Normal file
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
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

33
dpp/isa_detection.h Normal file
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
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
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;
}

73
dpp/json_interface.h Normal file
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
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
dpp/message.h Normal file

File diff suppressed because it is too large Load Diff

89
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
dpp/nlohmann/json.hpp Normal file

File diff suppressed because it is too large Load Diff

175
dpp/nlohmann/json_fwd.hpp Normal file
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
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
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
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
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
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
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
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
dpp/role.h Normal file

File diff suppressed because it is too large Load Diff

339
dpp/scheduled_event.h Normal file
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

53
dpp/signature_verifier.h Normal file
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
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
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
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);
}

154
dpp/socket_listener.h Normal file
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
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
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
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);
};
}

112
dpp/stage_instance.h Normal file
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
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
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
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
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);
};
}

105
dpp/timed_listener.h Normal file
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
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
dpp/unicode_emoji.h Normal file

File diff suppressed because it is too large Load Diff

584
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
dpp/utility.h Normal file

File diff suppressed because it is too large Load Diff

32
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
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
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
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;
}

33
dpp/win32_safe_warnings.h Normal file
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

106
dpp/wrapped_ssl_ctx.h Normal file
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
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
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);
};
}

221
main.cpp Normal file
View File

@ -0,0 +1,221 @@
#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"
//#include "ImageProcessor.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) {
// 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;
}

7988
stb_image.h Normal file

File diff suppressed because it is too large Load Diff

1724
stb_image_write.h Normal file

File diff suppressed because it is too large Load Diff