스킵 구현

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,470 @@
/************************************************************************************
*
* 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/user.h>
#include <dpp/guild.h>
#include <dpp/permissions.h>
#include <dpp/json_fwd.h>
#include <dpp/json_interface.h>
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 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: 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 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 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;
} // namespace dpp

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;
};
} // namespace dpp

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;
} // namespace dpp

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;
} // namespace dpp

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);
} // namespace dpp

View File

@@ -0,0 +1,882 @@
/************************************************************************************
*
* 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
*
* @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
*
* @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;
} // namespace dpp

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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;
};
} // namespace dpp

View File

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

View File

@@ -0,0 +1,428 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/snowflake.h>
#include <dpp/misc-enum.h>
#include <dpp/user.h>
#include <dpp/guild.h>
#include <dpp/role.h>
#include <dpp/appcommand.h>
#include <dpp/dispatcher.h>
#include <dpp/utility.h>
#include <dpp/json_fwd.h>
#include <dpp/event_router.h>
#include <unordered_map>
#include <vector>
#include <functional>
#include <variant>
namespace dpp {
/**
* @brief dpp::resolved_user contains both a dpp::guild_member and a dpp::user.
* The user can be used to obtain in-depth user details such as if they are nitro,
* and the guild member information to check their roles on a guild etc.
* The Discord API provides both if a parameter is a user ping,
* so we offer both in a combined structure.
*/
struct DPP_EXPORT resolved_user {
/**
* @brief Holds user information
*/
dpp::user user;
/**
* @brief Holds member information
*/
dpp::guild_member member;
};
/**
* @brief Represents a received parameter.
* We use variant so that multiple non-related types can be contained within.
*/
typedef std::variant<std::monostate, std::string, dpp::role, dpp::channel, dpp::resolved_user, int64_t, bool, double> command_parameter;
/**
* @brief Parameter types when registering a command.
* We don't pass these in when triggering the command in the handler, because it is
* expected the developer added the command so they know what types to expect for each named
* parameter.
*/
enum parameter_type {
/**
* @brief String parameter.
*/
pt_string,
/**
* @brief Role object parameter.
*/
pt_role,
/**
* @brief Channel object parameter.
*/
pt_channel,
/**
* @brief User object parameter.
*/
pt_user,
/**
* @brief 64 bit signed integer parameter.
*/
pt_integer,
/**
* @brief double floating point parameter.
*/
pt_double,
/**
* @brief Boolean parameter.
*/
pt_boolean
};
/**
* @brief Details of a command parameter used in registration.
* Note that for non-slash commands optional parameters can only be at the end of
* the list of parameters.
*/
struct DPP_EXPORT param_info {
/**
* @brief Type of parameter
*/
parameter_type type;
/**
* @brief True if the parameter is optional.
* For non-slash commands optional parameters may only be on the end of the list.
*/
bool optional;
/**
* @brief Description of command. Displayed only for slash commands
*/
std::string description;
/**
* @brief Allowed multiple choice options.
* The key name is the string passed to the command handler
* and the key value is its description displayed to the user.
*/
std::map<command_value, std::string> choices;
/**
* @brief Construct a new param_info object
*
* @param t Type of parameter
* @param o True if parameter is optional
* @param description The parameter description
* @param opts The options for a multiple choice parameter
*/
param_info(parameter_type t, bool o, const std::string &description, const std::map<command_value, std::string> &opts = {});
};
/**
* @brief Parameter list used during registration.
* Note that use of vector/pair is important here to preserve parameter order,
* as opposed to unordered_map (which doesn't guarantee any order at all) and
* std::map, which reorders keys alphabetically.
*/
typedef std::vector<std::pair<std::string, param_info>> parameter_registration_t;
/**
* @brief Parameter list for a called command.
* See dpp::parameter_registration_t for an explanation as to why vector is used.
*/
typedef std::vector<std::pair<std::string, command_parameter>> parameter_list_t;
/**
* @brief Represents the sending source of a command.
* This is passed to any command handler and should be passed back to
* commandhandler::reply(), allowing the reply method to route any replies back
* to the origin, which may be a slash command or a message. Both require different
* response facilities but we want this to be transparent if you use the command
* handler class.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
struct DPP_EXPORT command_source {
/**
* @brief Sending guild id
*/
snowflake guild_id;
/**
* @brief Source channel id
*/
snowflake channel_id;
/**
* @brief Command ID of a slash command
*/
snowflake command_id;
/**
* @brief Token for sending a slash command reply
*/
std::string command_token;
/**
* @brief The user who issued the command
*/
user issuer;
/**
* @brief Copy of the underlying message_create_t event, if it was a message create event
*/
std::optional<message_create_t> message_event;
/**
* @brief Copy of the underlying interaction_create_t event, if it was an interaction create event
*/
std::optional<interaction_create_t> interaction_event;
/**
* @brief Construct a command_source object from a message_create_t event
*/
command_source(const struct message_create_t& event);
/**
* @brief Construct a command_source object from an interaction_create_t event
*/
command_source(const struct interaction_create_t& event);
};
/**
* @brief The function definition for a command handler. Expects a command name string,
* and a list of command parameters.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
typedef std::function<void(const std::string&, const parameter_list_t&, command_source)> command_handler;
/**
* @brief Represents the details of a command added to the command handler class.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
struct DPP_EXPORT command_info_t {
/**
* @brief Function reference for the handler. This is std::function so it can represent
* a class member, a lambda or a raw C function pointer.
*/
command_handler func;
/**
* @brief Parameters requested for the command, with their types
*/
parameter_registration_t parameters;
/**
* @brief Guild ID the command exists on, or 0 to be present on all guilds
*/
snowflake guild_id;
};
/**
* @brief The commandhandler class represents a group of commands, prefixed or slash commands with handling functions.
*
* It can automatically register slash commands, and handle routing of messages and interactions to separated command handler
* functions.
* @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement.
*/
class DPP_EXPORT 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());
};
} // namespace dpp

View File

@@ -0,0 +1,28 @@
/************************************************************************************
*
* 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/async.h"
#include "coro/coroutine.h"
#include "coro/job.h"
#include "coro/task.h"
#include "coro/when_any.h"

View File

@@ -0,0 +1,523 @@
/************************************************************************************
*
* 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 async_dummy {
int* dummy_shared_state = nullptr;
};
}
#ifdef DPP_CORO
#include "coro.h"
#include <mutex>
#include <utility>
#include <type_traits>
#include <functional>
#include <atomic>
#include <cstddef>
namespace dpp {
namespace detail {
/**
* @brief Empty struct used for overload resolution.
*/
struct empty_tag_t{};
namespace async {
/**
* @brief Represents the step an std::async is at.
*/
enum class state_t {
/**
* @brief Request was sent but not co_await-ed. handle is nullptr, result_storage is not constructed.
*/
sent,
/**
* @brief Request was co_await-ed. handle is valid, result_storage is not constructed.
*/
waiting,
/**
* @brief Request was completed. handle is unknown, result_storage is valid.
*/
done,
/**
* @brief Request was never co_await-ed.
*/
dangling
};
/**
* @brief State of the async and its callback.
*
* Defined outside of dpp::async because this seems to work better with Intellisense.
*/
template <typename R>
struct async_callback_data {
/**
* @brief Number of references to this callback state.
*/
std::atomic<int> ref_count{1};
/**
* @brief State of the awaitable and the API callback
*/
std::atomic<state_t> state = state_t::sent;
/**
* @brief The stored result of the API call, stored as an array of bytes to directly construct in place
*/
alignas(R) std::array<std::byte, sizeof(R)> result_storage;
/**
* @brief Handle to the coroutine co_await-ing on this API call
*
* @see <a href="https://en.cppreference.com/w/cpp/coroutine/coroutine_handle">std::coroutine_handle</a>
*/
std_coroutine::coroutine_handle<> coro_handle = nullptr;
/**
* @brief Convenience function to construct the result in the storage and initialize its lifetime
*
* @warning This is only a convenience function, ONLY CALL THIS IN THE CALLBACK, before setting state to done.
*/
template <typename... Ts>
void construct_result(Ts&&... ts) {
// Standard-compliant type punning yay
std::construct_at<R>(reinterpret_cast<R *>(result_storage.data()), std::forward<Ts>(ts)...);
}
/**
* @brief Destructor.
*
* Also destroys the result if present.
*/
~async_callback_data() {
if (state.load() == state_t::done) {
std::destroy_at<R>(reinterpret_cast<R *>(result_storage.data()));
}
}
};
/**
* @brief Base class of dpp::async.
*
* @warning This class should not be used directly by a user, use dpp::async instead.
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::async so a user cannot call await_suspend and await_resume directly.
*/
template <typename R>
class async_base {
/**
* @brief Ref-counted callback, contains the callback logic and manages the lifetime of the callback data over multiple threads.
*/
struct shared_callback {
/**
* @brief Self-managed ref-counted pointer to the state data
*/
async_callback_data<R> *state = new async_callback_data<R>;
/**
* @brief Callback function.
*
* Constructs the callback data, and if the coroutine was awaiting, resume it
* @param cback The result of the API call.
* @tparam V Forwarding reference convertible to R
*/
template <std::convertible_to<R> V>
void operator()(V &&cback) const {
state->construct_result(std::forward<V>(cback));
if (auto previous_state = state->state.exchange(state_t::done); previous_state == state_t::waiting) {
state->coro_handle.resume();
}
}
/**
* @brief Main constructor, allocates a new callback_state object.
*/
shared_callback() = default;
/**
* @brief Empty constructor, holds no state.
*/
explicit shared_callback(detail::empty_tag_t) noexcept : state{nullptr} {}
/**
* @brief Copy constructor. Takes shared ownership of the callback state, increasing the reference count.
*/
shared_callback(const shared_callback &other) noexcept {
this->operator=(other);
}
/**
* @brief Move constructor. Transfers ownership from another object, leaving intact the reference count. The other object releases the callback state.
*/
shared_callback(shared_callback &&other) noexcept {
this->operator=(std::move(other));
}
/**
* @brief Destructor. Releases the held reference and destroys if no other references exist.
*/
~shared_callback() {
if (!state) { // Moved-from object
return;
}
auto count = state->ref_count.fetch_sub(1);
if (count == 0) {
delete state;
}
}
/**
* @brief Copy assignment. Takes shared ownership of the callback state, increasing the reference count.
*/
shared_callback &operator=(const shared_callback &other) noexcept {
state = other.state;
++state->ref_count;
return *this;
}
/**
* @brief Move assignment. Transfers ownership from another object, leaving intact the reference count. The other object releases the callback state.
*/
shared_callback &operator=(shared_callback &&other) noexcept {
state = std::exchange(other.state, nullptr);
return *this;
}
/**
* @brief Function called by the async when it is destroyed when it was never co_awaited, signals to the callback to abort.
*/
void set_dangling() noexcept {
if (!state) { // moved-from object
return;
}
state->state.store(state_t::dangling);
}
bool done(std::memory_order order = std::memory_order_seq_cst) const noexcept {
return (state->state.load(order) == state_t::done);
}
/**
* @brief Convenience function to get the shared callback state's result.
*
* @warning It is UB to call this on a callback whose state is anything else but state_t::done.
*/
R &get_result() noexcept {
assert(state && done());
return (*reinterpret_cast<R *>(state->result_storage.data()));
}
/**
* @brief Convenience function to get the shared callback state's result.
*
* @warning It is UB to call this on a callback whose state is anything else but state_t::done.
*/
const R &get_result() const noexcept {
assert(state && done());
return (*reinterpret_cast<R *>(state->result_storage.data()));
}
};
/**
* @brief Shared state of the async and its callback, to be used across threads.
*/
shared_callback api_callback{nullptr};
public:
/**
* @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_base(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} {
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_base(Fun &&fun, Args&&... args) : api_callback{} {
std::invoke(std::forward<Fun>(fun), std::forward<Args>(args)..., api_callback);
}
/**
* @brief Construct an empty async. Using `co_await` on an empty async is undefined behavior.
*/
async_base() noexcept : api_callback{detail::empty_tag_t{}} {}
/**
* @brief Destructor. If any callback is pending it will be aborted.
*/
~async_base() {
api_callback.set_dangling();
}
/**
* @brief Copy constructor is disabled
*/
async_base(const async_base &) = delete;
/**
* @brief Move constructor
*
* NOTE: Despite being marked noexcept, this function uses std::lock_guard which may throw. The implementation assumes this can never happen, hence noexcept. Report it if it does, as that would be a bug.
*
* @remark Using the moved-from async after this function is undefined behavior.
* @param other The async object to move the data from.
*/
async_base(async_base &&other) noexcept = default;
/**
* @brief Copy assignment is disabled
*/
async_base &operator=(const async_base &) = delete;
/**
* @brief Move assignment operator.
*
* NOTE: Despite being marked noexcept, this function uses std::lock_guard which may throw. The implementation assumes this can never happen, hence noexcept. Report it if it does, as that would be a bug.
*
* @remark Using the moved-from async after this function is undefined behavior.
* @param other The async object to move the data from
*/
async_base &operator=(async_base &&other) noexcept = default;
/**
* @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 of the API call or not
*/
[[nodiscard]] bool await_ready() const noexcept {
return api_callback.done();
}
/**
* @brief Second function called by the standard library when the object is co-awaited, if await_ready returned false.
*
* Checks again for the presence of the result, if absent, signals to suspend and keep track of the calling coroutine for the callback to resume.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @param caller The handle to the coroutine co_await-ing and being suspended
*/
[[nodiscard]] bool await_suspend(detail::std_coroutine::coroutine_handle<> caller) noexcept {
auto sent = state_t::sent;
api_callback.state->coro_handle = caller;
return api_callback.state->state.compare_exchange_strong(sent, state_t::waiting); // true (suspend) if `sent` was replaced with `waiting` -- false (resume) if the value was not `sent` (`done` is the only other option)
}
/**
* @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to
*
* @remark Do not call this manually, use the co_await keyword instead.
* @return The result of the API call as an lvalue reference.
*/
R& await_resume() & noexcept {
return api_callback.get_result();
}
/**
* @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to
*
* @remark Do not call this manually, use the co_await keyword instead.
* @return The result of the API call as a const lvalue reference.
*/
const R& await_resume() const& noexcept {
return api_callback.get_result();
}
/**
* @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to
*
* @remark Do not call this manually, use the co_await keyword instead.
* @return The result of the API call as an rvalue reference.
*/
R&& await_resume() && noexcept {
return std::move(api_callback.get_result());
}
};
} // 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 : private detail::async::async_base<R> {
/**
* @brief Internal use only base class. It serves to prevent await_suspend and await_resume from being used directly.
*
* @warning For internal use only, do not use.
* @see operator co_await()
*/
friend class detail::async::async_base<R>;
public:
using detail::async::async_base<R>::async_base; // use async_base's constructors. unfortunately on clang this doesn't include the templated ones so we have to delegate below
using detail::async::async_base<R>::operator=; // use async_base's assignment operator
using detail::async::async_base<R>::await_ready; // expose await_ready as public
/**
* @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) : detail::async::async_base<R>{std::forward<Obj>(obj), std::forward<Fun>(fun), std::forward<Args>(args)...} {}
/**
* @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) : detail::async::async_base<R>{std::forward<Fun>(fun), std::forward<Args>(args)...} {}
#ifdef _DOXYGEN_ // :)
/**
* @brief Construct an empty async. Using `co_await` on an empty async is undefined behavior.
*/
async() noexcept;
/**
* @brief Destructor. If any callback is pending it will be aborted.
*/
~async();
/**
* @brief Copy constructor is disabled
*/
async(const async &);
/**
* @brief Move constructor
*
* NOTE: Despite being marked noexcept, this function uses std::lock_guard which may throw. The implementation assumes this can never happen, hence noexcept. Report it if it does, as that would be a bug.
*
* @remark Using the moved-from async after this function is undefined behavior.
* @param other The async object to move the data from.
*/
async(async &&other) noexcept = default;
/**
* @brief Copy assignment is disabled
*/
async &operator=(const async &) = delete;
/**
* @brief Move assignment operator.
*
* NOTE: Despite being marked noexcept, this function uses std::lock_guard which may throw. The implementation assumes this can never happen, hence noexcept. Report it if it does, as that would be a bug.
*
* @remark Using the moved-from async after this function is undefined behavior.
* @param other The async object to move the data from
*/
async &operator=(async &&other) noexcept = default;
/**
* @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 of the API call or not
*/
[[nodiscard]] bool await_ready() const noexcept;
#endif
/**
* @brief Suspend the caller until the request completes.
*
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::async::async_base<R>&>(*this);
}
/**
* @brief Suspend the caller until the request completes.
*
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::async::async_base<R> const&>(*this);
}
/**
* @brief Suspend the caller until the request completes.
*
* @return On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::async::async_base<R>&&>(*this);
}
};
DPP_CHECK_ABI_COMPAT(async<>, async_dummy);
} // namespace dpp
#endif /* DPP_CORO */

View File

@@ -0,0 +1,186 @@
/************************************************************************************
*
* 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.
*
************************************************************************************/
#ifdef DPP_CORO
#pragma once
#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>
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>
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>
bool has_await_members;
/**
* @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
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_CORO */

View File

@@ -0,0 +1,589 @@
/************************************************************************************
*
* 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;
};
}
#ifdef DPP_CORO
#include "coro.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>>;
/**
* @brief Base class of dpp::coroutine<R>.
*
* @warn This class should not be used directly by a user, use dpp::coroutine<R> instead.
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::coroutine<R> so a user cannot call await_suspend and await_resume directly.
*/
template <typename R>
class coroutine_base {
protected:
/**
* @brief Promise has friend access for the constructor
*/
friend struct promise_t<R>;
/**
* @brief Coroutine handle.
*/
detail::coroutine::handle_t<R> handle{nullptr};
private:
/**
* @brief Construct from a handle. Internal use only.
*/
coroutine_base(detail::coroutine::handle_t<R> h) : handle{h} {}
public:
/**
* @brief Default constructor, creates an empty coroutine.
*/
coroutine_base() = default;
/**
* @brief Copy constructor is disabled
*/
coroutine_base(const coroutine_base &) = delete;
/**
* @brief Move constructor, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine_base(coroutine_base &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {}
/**
* @brief Destructor, destroys the handle.
*/
~coroutine_base() {
if (handle) {
handle.destroy();
}
}
/**
* @brief Copy assignment is disabled
*/
coroutine_base &operator=(const coroutine_base &) = delete;
/**
* @brief Move assignment, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine_base &operator=(coroutine_base &&other) noexcept {
handle = std::exchange(other.handle, nullptr);
return *this;
}
/**
* @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 (!handle) {
throw dpp::logic_exception("cannot co_await an empty coroutine");
}
return 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]] handle_t<R> await_suspend(detail::std_coroutine::coroutine_handle<T> caller) noexcept {
handle.promise().parent = caller;
return handle;
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throw Throws any exception thrown or uncaught by the coroutine
* @return R The result of the coroutine. It is given to the caller as a result to `co_await`
*/
decltype(auto) await_resume() & {
return static_cast<dpp::coroutine<R> &>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throw Throws any exception thrown or uncaught by the coroutine
* @return R The result of the coroutine. It is given to the caller as a result to `co_await`
*/
[[nodiscard]] decltype(auto) await_resume() const & {
return static_cast<dpp::coroutine<R> const&>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throw Throws any exception thrown or uncaught by the coroutine
* @return R The result of the coroutine. It is given to the caller as a result to `co_await`
*/
[[nodiscard]] decltype(auto) await_resume() && {
return static_cast<dpp::coroutine<R> &&>(*this).await_resume_impl();
}
};
} // 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 coroutine : private detail::coroutine::coroutine_base<R> {
/**
* @brief Internal use only base class containing common logic between coroutine<R> and coroutine<void>. It also serves to prevent await_suspend and await_resume from being used directly.
*
* @warning For internal use only, do not use.
* @see operator co_await()
*/
friend class detail::coroutine::coroutine_base<R>;
[[nodiscard]] R& await_resume_impl() & {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *promise.result;
}
[[nodiscard]] const R& await_resume_impl() const & {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *promise.result;
}
[[nodiscard]] R&& await_resume_impl() && {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *std::move(promise.result);
}
public:
#ifdef _DOXYGEN_ // :))))
/**
* @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;
/**
* @brief Destructor, destroys the handle.
*/
~coroutine();
/**
* @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;
/**
* @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;
#else
using detail::coroutine::coroutine_base<R>::coroutine_base; // use coroutine_base's constructors
using detail::coroutine::coroutine_base<R>::operator=; // use coroutine_base's assignment operators
using detail::coroutine::coroutine_base<R>::await_ready; // expose await_ready as public
#endif
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::coroutine::coroutine_base<R>&>(*this);
}
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::coroutine::coroutine_base<R> const&>(*this);
}
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::coroutine::coroutine_base<R>&&>(*this);
}
};
#ifndef _DOXYGEN_ // don't generate this on doxygen because `using` doesn't work and 2 copies of coroutine_base's docs is enough
/**
* @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 the <a href="https://discord.gg/dpp">D++ 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 <>
class coroutine<void> : private detail::coroutine::coroutine_base<void> {
/**
* @brief Base class has friend access for CRTP downcast
*/
friend class detail::coroutine::coroutine_base<void>;
void await_resume_impl() const;
public:
using detail::coroutine::coroutine_base<void>::coroutine_base; // use coroutine_base's constructors
using detail::coroutine::coroutine_base<void>::operator=; // use coroutine_base's assignment operators
using detail::coroutine::coroutine_base<void>::await_ready; // expose await_ready as public
/**
* @brief Suspend the current coroutine until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::coroutine::coroutine_base<void>&>(*this);
}
/**
* @brief Suspend the current coroutine until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::coroutine::coroutine_base<void> const &>(*this);
}
/**
* @brief Suspend the current coroutine until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::coroutine::coroutine_base<void>&&>(*this);
}
};
#endif /* _DOXYGEN_ */
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
#ifndef _DOXYGEN_
inline void coroutine<void>::await_resume_impl() const {
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
}
#endif /* _DOXYGEN_ */
DPP_CHECK_ABI_COMPAT(coroutine<void>, coroutine_dummy)
DPP_CHECK_ABI_COMPAT(coroutine<uint64_t>, coroutine_dummy)
} // namespace dpp
/**
* @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_CORO */

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 {
};
}
#ifdef DPP_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_CORO */

View File

@@ -0,0 +1,784 @@
/************************************************************************************
*
* 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 task_dummy {
int* handle_dummy = nullptr;
};
}
#ifdef DPP_CORO
#include "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 State of a task
*/
enum class state_t {
/**
* @brief Task was started but never co_await-ed
*/
started,
/**
* @brief Task was co_await-ed and is pending completion
*/
awaited,
/**
* @brief Task is completed
*/
done,
/**
* @brief Task is still running but the actual dpp::task object is destroyed
*/
dangling
};
/**
* @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>>;
/**
* @brief Base class of @ref dpp::task.
*
* @warning This class should not be used directly by a user, use @ref dpp::task instead.
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of @ref dpp::task so a user cannot call await_suspend() and await_resume() directly.
*/
template <typename R>
class task_base {
protected:
/**
* @brief The coroutine handle of this task.
*/
handle_t<R> handle;
/**
* @brief Promise type of this coroutine. For internal use only, do not use.
*/
friend struct promise_t<R>;
private:
/**
* @brief Construct from a coroutine handle. Internal use only
*/
explicit task_base(handle_t<R> handle_) : handle(handle_) {}
public:
/**
* @brief Default constructor, creates a task not bound to a coroutine.
*/
task_base() = default;
/**
* @brief Copy constructor is disabled
*/
task_base(const task_base &) = delete;
/**
* @brief Move constructor, grabs another task's coroutine handle
*
* @param other Task to move the handle from
*/
task_base(task_base &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {}
/**
* @brief Destructor.
*
* Destroys the handle.
* @warning The coroutine must be finished before this is called, otherwise it runs the risk of being resumed after it is destroyed, resuming in use-after-free undefined behavior.
*/
~task_base() {
if (handle) {
promise_t<R> &promise = handle.promise();
state_t previous_state = promise.state.exchange(state_t::dangling);
if (previous_state == state_t::done) {
handle.destroy();
}
else {
cancel();
}
}
}
/**
* @brief Copy assignment is disabled
*/
task_base &operator=(const task_base &) = delete;
/**
* @brief Move assignment, grabs another task's coroutine handle
*
* @param other Task to move the handle from
*/
task_base &operator=(task_base &&other) noexcept {
handle = std::exchange(other.handle, nullptr);
return (*this);
}
/**
* @brief Check whether or not a call to co_await will suspend the caller.
*
* This function is called by the standard library as a first step when using co_await. If it returns true then the caller is not suspended.
* @throws logic_exception if the task is empty.
* @return bool Whether not to suspend the caller or not
*/
[[nodiscard]] bool await_ready() const {
if (!handle) {
throw dpp::logic_exception{"cannot co_await an empty task"};
}
return handle.promise().state.load() == state_t::done;
}
/**
* @brief Second function called by the standard library when the task is co_await-ed, if await_ready returned false.
*
* Stores the calling coroutine in the promise to resume when this task suspends.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @param caller The calling coroutine, now suspended
* @return bool Whether to suspend the caller or not
*/
[[nodiscard]] bool await_suspend(std_coroutine::coroutine_handle<> caller) noexcept {
promise_t<R> &my_promise = handle.promise();
auto previous_state = state_t::started;
my_promise.parent = caller;
// Replace `sent` state with `awaited` ; if that fails, the only logical option is the state was `done`, in which case return false to resume
if (!handle.promise().state.compare_exchange_strong(previous_state, state_t::awaited) && previous_state == state_t::done) {
return false;
}
return true;
}
/**
* @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 && handle.promise().state.load(std::memory_order_relaxed) == state_t::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
*/
dpp::task<R>& cancel() & noexcept {
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
return static_cast<dpp::task<R> &>(*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
*/
dpp::task<R>&& cancel() && noexcept {
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
return static_cast<dpp::task<R> &&>(*this);
}
/**
* @brief Function called by the standard library when resuming.
*
* @return Return value of the coroutine, handed to the caller of co_await.
*/
decltype(auto) await_resume() & {
return static_cast<dpp::task<R> &>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when resuming.
*
* @return Return value of the coroutine, handed to the caller of co_await.
*/
decltype(auto) await_resume() const & {
return static_cast<const dpp::task<R> &>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when resuming.
*
* @return Return value of the coroutine, handed to the caller of co_await.
*/
decltype(auto) await_resume() && {
return static_cast<dpp::task<R> &&>(*this).await_resume_impl();
}
};
} // 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 task : private detail::task::task_base<R> {
/**
* @brief Internal use only base class containing common logic between task<R> and task<void>. It also serves to prevent await_suspend and await_resume from being used directly.
*
* @warning For internal use only, do not use.
* @see operator co_await()
*/
friend class detail::task::task_base<R>;
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @throw Throws any exception thrown or uncaught by the coroutine
* @return The result of the coroutine. This is returned to the awaiter as the result of co_await
*/
R& await_resume_impl() & {
detail::task::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *reinterpret_cast<R *>(promise.result_storage.data());
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @throw Throws any exception thrown or uncaught by the coroutine
* @return The result of the coroutine. This is returned to the awaiter as the result of co_await
*/
const R& await_resume_impl() const & {
detail::task::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *reinterpret_cast<R *>(promise.result_storage.data());
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @throw Throws any exception thrown or uncaught by the coroutine
* @return The result of the coroutine. This is returned to the awaiter as the result of co_await
*/
R&& await_resume_impl() && {
detail::task::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *reinterpret_cast<R *>(promise.result_storage.data());
}
public:
#ifdef _DOXYGEN_ // :)
/**
* @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;
/**
* @brief Destructor.
*
* Destroys the handle.
* @warning The coroutine must be finished before this is called, otherwise it runs the risk of being resumed after it is destroyed, resuming in use-after-free undefined behavior.
*/
~task();
/**
* @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;
/**
* @brief Function to check if the task has finished its execution entirely
*
* @return bool Whether the task is finished.
*/
[[nodiscard]] bool done() const noexcept;
/**
* @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.
*/
dpp::task<R>& cancel() & noexcept;
/**
* @brief Check whether or not a call to co_await will suspend the caller.
*
* This function is called by the standard library as a first step when using co_await. If it returns true then the caller is not suspended.
* @throws logic_exception if the task is empty.
* @return bool Whether not to suspend the caller or not
*/
[[nodiscard]] bool await_ready() const;
#else
using detail::task::task_base<R>::task_base; // use task_base's constructors
using detail::task::task_base<R>::operator=; // use task_base's assignment operators
using detail::task::task_base<R>::done; // expose done() as public
using detail::task::task_base<R>::cancel; // expose cancel() as public
using detail::task::task_base<R>::await_ready; // expose await_ready as public
#endif
/**
* @brief Suspend the current coroutine until the task completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::task::task_base<R>&>(*this);
}
/**
* @brief Suspend the current coroutine until the task completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<const detail::task::task_base<R>&>(*this);
}
/**
* @brief Suspend the current coroutine until the task completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::task::task_base<R>&&>(*this);
}
};
#ifndef _DOXYGEN_ // don't generate this on doxygen because `using` doesn't work and 2 copies of coroutine_base's docs is enough
/**
* @brief A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for parallel coroutines returning a value.
*
* Can be used in conjunction with coroutine events via dpp::event_router_t::co_attach, or on its own.
*
* @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 Return type of the coroutine. Cannot be a reference, can be void.
*/
template <>
class task<void> : private detail::task::task_base<void> {
/**
* @brief Private base class containing common logic between task<R> and task<void>. It also serves to prevent await_suspend and await_resume from being used directly.
*
* @see operator co_await()
*/
friend class detail::task::task_base<void>;
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throw Throws any exception thrown or uncaught by the coroutine
*/
void await_resume_impl() const;
public:
using detail::task::task_base<void>::task_base; // use task_base's constructors
using detail::task::task_base<void>::operator=; // use task_base's assignment operators
using detail::task::task_base<void>::done; // expose done() as public
using detail::task::task_base<void>::cancel; // expose cancel() as public
using detail::task::task_base<void>::await_ready; // expose await_ready as public
/**
* @brief Suspend the current coroutine until the task completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, returns a reference to the contained result.
*/
auto& operator co_await() & {
return static_cast<detail::task::task_base<void>&>(*this);
}
/**
* @brief Suspend the current coroutine until the task completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, returns a const reference to the contained result.
*/
const auto& operator co_await() const & {
return static_cast<const detail::task::task_base<void>&>(*this);
}
/**
* @brief Suspend the current coroutine until the task completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, returns a reference to the contained result.
*/
auto&& operator co_await() && {
return static_cast<detail::task::task_base<void>&&>(*this);
}
};
#endif /* _DOXYGEN_ */
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
*/
struct promise_base {
/**
* @brief State of the task, used to keep track of lifetime and status
*/
std::atomic<state_t> state = state_t::started;
/**
* @brief Whether the task is cancelled or not.
*/
std::atomic<bool> cancelled = false;
/**
* @brief Parent coroutine to return to for nested coroutines.
*/
detail::std_coroutine::coroutine_handle<> parent = nullptr;
/**
* @brief Exception ptr if any was thrown during the coroutine
*
* @see <a href="https://en.cppreference.com/w/cpp/error/exception_ptr>std::exception_ptr</a>
*/
std::exception_ptr exception = nullptr;
#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.
*/
[[nodiscard]] 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() {
exception = std::current_exception();
if ((state.load() == task::state_t::dangling) && !cancelled) {
throw;
}
}
/**
* @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 task::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 <typename 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 {
/**
* @brief Destructor. Destroys the value if it was constructed.
*/
~promise_t() {
if (state.load() == state_t::done && !exception) {
std::destroy_at(reinterpret_cast<R *>(result_storage.data()));
}
}
/**
* @brief Stored return value of the coroutine.
*
*/
alignas(R) std::array<std::byte, sizeof(R)> result_storage;
/**
* @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> {
std::construct_at<R>(reinterpret_cast<R *>(result_storage.data()), 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> {
std::construct_at<R>(reinterpret_cast<R *>(result_storage.data()), 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>) {
std::construct_at<R>(reinterpret_cast<R *>(result_storage.data()), 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 {
/**
* @brief Function called by the standard library when the coroutine co_returns
*
* Does nothing but is required by the standard library.
*/
void return_void() const noexcept {}
/**
* @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 {
promise_t<R> &promise = handle.promise();
state_t previous_state = promise.state.exchange(state_t::done);
switch (previous_state) {
case state_t::started: // started but never awaited, suspend
return std_coroutine::noop_coroutine();
case state_t::awaited: // co_await-ed, resume parent
return promise.parent;
case state_t::dangling: // task object is gone, free the handle
handle.destroy();
return std_coroutine::noop_coroutine();
case state_t::done: // what
// this should never happen. log it. we don't have a cluster so just write it on cerr
std::cerr << "dpp::task: final_suspend called twice. something went very wrong here, please report to GitHub issues or the D++ Discord server" << std::endl;
}
// TODO: replace with __builtin_unreachable when we confirm this never happens with normal usage
return std_coroutine::noop_coroutine();
}
} // namespace detail::task
#ifndef _DOXYGEN_
inline void task<void>::await_resume_impl() const {
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
}
#endif /* _DOXYGEN_ */
DPP_CHECK_ABI_COMPAT(task<void>, task_dummy)
DPP_CHECK_ABI_COMPAT(task<uint64_t>, task_dummy)
} // namespace dpp
/**
* @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_CORO */

View File

@@ -0,0 +1,532 @@
/************************************************************************************
*
* 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.
*
************************************************************************************/
#ifdef DPP_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 class await_state {
/**
* @brief Object was started but not awaited
*/
started,
/**
* @brief Object is being awaited
*/
waiting,
/**
* @brief Object was resumed
*/
done,
/**
* @brief Object was destroyed
*/
dangling
};
/**
* @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<detail::when_any::await_state> owner_state{detail::when_any::await_state::started};
};
/**
* @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 dpp::job make_job(std::shared_ptr<state_t> shared_state) {
/**
* 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>, detail::when_any::empty>) {
decltype(auto) result = co_await std::get<N>(shared_state->awaitables);
if (auto s = shared_state->owner_state.load(std::memory_order_relaxed); s == detail::when_any::await_state::dangling || s == detail::when_any::await_state::done) {
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.load(std::memory_order_relaxed); s == detail::when_any::await_state::dangling || s == detail::when_any::await_state::done) {
co_return;
}
shared_state->result.template emplace<N + 1>();
}
if (shared_state->owner_state.exchange(detail::when_any::await_state::done) != detail::when_any::await_state::waiting) {
co_return;
}
if (auto handle = shared_state->handle; handle) {
shared_state->index_finished = N;
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() {
[]<size_t... Ns>(when_any *self, std::index_sequence<Ns...>) {
(make_job<Ns>(self->my_state), ...);
}(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 {
auto sent = detail::when_any::await_state::started;
self->my_state->handle = caller;
return self->my_state->owner_state.compare_exchange_strong(sent, detail::when_any::await_state::waiting); // true (suspend) if `started` was replaced with `waiting` -- false (resume) if the value was not `started` (`done` is the only other option)
}
/**
* @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 == detail::when_any::await_state::done;
}
/**
* @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,526 @@
/************************************************************************************
*
* 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 <deque>
#include <mutex>
#include <shared_mutex>
#define DISCORD_API_VERSION "10"
#define API_PATH "/api/v" DISCORD_API_VERSION
namespace dpp {
// Forward declarations
class cluster;
/**
* @brief This is an opaque class containing zlib library specific structures.
* We define it this way so that the public facing D++ library doesn't require
* the zlib headers be available to build against it.
*/
class zlibcontext;
/**
* @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 Construct a new voiceconn object
*/
voiceconn() = default;
/**
* @brief Construct a new voiceconn object
*
* @param o owner
* @param _channel_id voice channel id
*/
voiceconn(class discord_client* o, snowflake _channel_id);
/**
* @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();
/**
* @brief return true if the connection is active (websocket exists)
*
* @return true if has an active websocket
*/
bool is_active();
/**
* @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 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 True if the shard is terminating
*/
bool terminating;
/**
* @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);
private:
/**
* @brief Mutex for message queue
*/
std::shared_mutex queue_mutex;
/**
* @brief Queue of outbound messages
*/
std::deque<std::string> message_queue;
/**
* @brief Thread this shard is executing on
*/
std::thread* runner;
/**
* @brief Run shard loop under a thread.
* Calls discord_client::run() from within a std::thread.
*/
void thread_run();
/**
* @brief If true, stream compression is enabled
*/
bool compressed;
/**
* @brief ZLib decompression buffer
*/
unsigned char* decomp_buffer;
/**
* @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.
*/
zlibcontext* zlib;
/**
* @brief Total decompressed received bytes
*/
uint64_t decompressed_total;
/**
* @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
*/
class 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 Initialise ZLib (websocket compression)
* @throw dpp::exception if ZLib cannot be initialised
*/
void setup_zlib();
/**
* @brief Shut down ZLib (websocket compression)
*/
void end_zlib();
/**
* @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 Thread ID
*/
std::thread::native_handle_type thread_id;
/**
* @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;
/**
* @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();
/** Fires every second from the underlying socket I/O loop, used for sending heartbeats */
virtual void one_second_timer();
/**
* @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 Destroy the discord client object
*/
virtual ~discord_client();
/**
* @brief Get the decompressed bytes in objectGet decompressed total bytes received
* @return uint64_t bytes received
*/
uint64_t get_decompressed_bytes_in();
/**
* @brief Handle JSON from the websocket.
* @param buffer The entire buffer content from the websocket client
* @returns True if a frame has been handled
*/
virtual bool handle_frame(const std::string &buffer);
/**
* @brief Handle a websocket error.
* @param errorcode The error returned from the websocket
*/
virtual void error(uint32_t errorcode);
/**
* @brief Start and monitor I/O loop.
* @note this is a blocking call and is usually executed within a
* thread by whatever creates the object.
*/
void run();
/**
* @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
* @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);
/**
* @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);
};
} // namespace dpp

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 j 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 keyname 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);
} // namespace dpp

View File

@@ -0,0 +1,970 @@
/************************************************************************************
*
* 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 <cerrno>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <fcntl.h>
#include <csignal>
#include <cstring>
#include <string>
#include <map>
#include <vector>
#include <dpp/json_fwd.h>
#include <dpp/wsclient.h>
#include <dpp/dispatcher.h>
#include <dpp/cluster.h>
#include <dpp/discordevents.h>
#include <dpp/socket.h>
#include <queue>
#include <thread>
#include <deque>
#include <mutex>
#include <shared_mutex>
#include <memory>
#include <future>
#include <functional>
#include <chrono>
struct OpusDecoder;
struct OpusEncoder;
struct OpusRepacketizer;
namespace dpp {
class audio_mixer;
// !TODO: change these to constexpr and rename every occurrence across the codebase
#define AUDIO_TRACK_MARKER (uint16_t)0xFFFF
#define AUDIO_OVERLAP_SLEEP_SAMPLES 30
inline constexpr size_t send_audio_raw_max_length = 11520;
/*
* @brief For holding a moving average of the number of current voice users, for applying a smooth gain ramp.
*/
struct DPP_EXPORT moving_averager {
moving_averager() = default;
moving_averager(uint64_t collection_count_new);
moving_averager operator+=(int64_t value);
operator float();
protected:
std::deque<int64_t> values{};
uint64_t collectionCount{};
};
// Forward declaration
class cluster;
/**
* @brief An opus-encoded RTP packet to be sent out to a voice channel
*/
struct DPP_EXPORT voice_out_packet {
/**
* @brief Each string is a UDP packet.
* Generally these will be RTP.
*/
std::string packet;
/**
* @brief Duration of packet
*/
uint64_t duration;
};
/** @brief Implements a discord voice connection.
* Each discord_voice_client connects to one voice channel and derives from a websocket client.
*/
class DPP_EXPORT discord_voice_client : public websocket_client
{
/**
* @brief Clean up resources
*/
void cleanup();
/**
* @brief Mutex for outbound packet stream
*/
std::mutex stream_mutex;
/**
* @brief Mutex for message queue
*/
std::shared_mutex queue_mutex;
/**
* @brief Queue of outbound messages
*/
std::deque<std::string> message_queue;
/**
* @brief Thread this connection is executing on
*/
std::thread* runner;
/**
* @brief Run shard loop under a thread
*/
void thread_run();
/**
* @brief Last connect time of voice session
*/
time_t connect_time;
/*
* @brief For mixing outgoing voice data.
*/
std::unique_ptr<audio_mixer> mixer;
/**
* @brief IP of UDP/RTP endpoint
*/
std::string ip;
/**
* @brief Port number of UDP/RTP endpoint
*/
uint16_t port;
/**
* @brief SSRC value
*/
uint64_t ssrc;
/**
* @brief List of supported audio encoding modes
*/
std::vector<std::string> modes;
/**
* @brief Timescale in nanoseconds
*/
uint64_t timescale;
/**
* @brief Output buffer
*/
std::vector<voice_out_packet> outbuf;
/**
* @brief Data type of RTP packet sequence number field.
*/
using rtp_seq_t = uint16_t;
using rtp_timestamp_t = uint32_t;
/**
* @brief Keeps track of the voice payload to deliver to voice handlers.
*/
struct voice_payload {
/**
* @brief The sequence number of the RTP packet that generated this
* voice payload.
*/
rtp_seq_t seq;
/**
* @brief The timestamp of the RTP packet that generated this voice
* payload.
*
* The timestamp is used to detect the order around where sequence
* number wraps around.
*/
rtp_timestamp_t timestamp;
/**
* @brief The event payload that voice handlers receive.
*/
std::unique_ptr<voice_receive_t> vr;
/**
* @brief For priority_queue sorting.
* @return true if "this" has lower priority that "other",
* i.e. appears later in the queue; false otherwise.
*/
bool operator<(const voice_payload& other) const;
};
struct voice_payload_parking_lot {
/**
* @brief The range of RTP packet sequence number and timestamp in the lot.
*
* The minimum is used to drop packets that arrive too late. Packets
* less than the minimum have been delivered to voice handlers and
* there is no going back. Unfortunately we just have to drop them.
*
* The maximum is used, at flush time, to calculate the minimum for
* the next batch. The maximum is also updated every time we receive an
* RTP packet with a larger value.
*/
struct seq_range_t {
rtp_seq_t min_seq, max_seq;
rtp_timestamp_t min_timestamp, max_timestamp;
} range;
/**
* @brief The queue of parked voice payloads.
*
* We group payloads and deliver them to handlers periodically as the
* handling of out-of-order RTP packets. Payloads in between flushes
* are parked and sorted in this queue.
*/
std::priority_queue<voice_payload> parked_payloads;
/**
* @brief The decoder ctls to be set on the decoder.
*/
std::vector<std::function<void(OpusDecoder&)>> pending_decoder_ctls;
/**
* @brief libopus decoder
*
* Shared with the voice courier thread that does the decoding.
* This is not protected by a mutex because only the courier thread
* uses the decoder.
*/
std::shared_ptr<OpusDecoder> decoder;
};
/**
* @brief Thread used to deliver incoming voice data to handlers.
*/
std::thread voice_courier;
/**
* @brief Shared state between this voice client and the courier thread.
*/
struct courier_shared_state_t {
/**
* @brief Protects all following members.
*/
std::mutex mtx;
/**
* @brief Signaled when there is a new payload to deliver or terminating state has changed.
*/
std::condition_variable signal_iteration;
/**
* @brief Voice buffers to be reported to handler, grouped by speaker.
*
* Buffers are parked here and flushed every 500ms.
*/
std::map<snowflake, voice_payload_parking_lot> parked_voice_payloads;
/**
* @brief Used to signal termination.
*
* @note Pending payloads are delivered first before termination.
*/
bool terminating = false;
} voice_courier_shared_state;
/**
* @brief The run loop of the voice courier thread.
*/
static void voice_courier_loop(discord_voice_client&, courier_shared_state_t&);
/**
* @brief If true, audio packet sending is paused
*/
bool paused;
#ifdef HAVE_VOICE
/**
* @brief libopus encoder
*/
OpusEncoder* encoder;
/**
* @brief libopus repacketizer
* (merges frames into one packet)
*/
OpusRepacketizer* repacketizer;
#else
/**
* @brief libopus encoder
*/
void* encoder;
/**
* @brief libopus repacketizer
* (merges frames into one packet)
*/
void* repacketizer;
#endif
/**
* @brief File descriptor for UDP connection
*/
dpp::socket fd;
/**
* @brief Secret key for encrypting voice.
* If it has been sent, this is non-null and points to a
* sequence of exactly 32 bytes.
*/
uint8_t* secret_key;
/**
* @brief Sequence number of outbound audio. This is incremented
* once per frame sent.
*/
uint16_t sequence;
/**
* @brief Timestamp value used in outbound audio. Each packet
* has the timestamp value which is incremented to match
* how many frames are sent.
*/
uint32_t timestamp;
/**
* @brief Last sent packet high-resolution timestamp
*/
std::chrono::high_resolution_clock::time_point last_timestamp;
/**
* @brief Fraction of the sleep that was not executed after the last audio packet was sent
*/
std::chrono::nanoseconds last_sleep_remainder;
/**
* @brief Maps receiving ssrc to user id
*/
std::unordered_map<uint32_t, snowflake> ssrc_map;
/**
* @brief This is set to true if we have started sending audio.
* When this moves from false to true, this causes the
* client to send the 'talking' notification to the websocket.
*/
bool sending;
/**
* @brief Number of track markers in the buffer. For example if there
* are two track markers in the buffer there are 3 tracks.
*
* **Special case:**
*
* If the buffer is empty, there are zero tracks in the
* buffer.
*/
uint32_t tracks;
/**
* @brief Meta data associated with each track.
* Arbitrary string that the user can set via
* dpp::discord_voice_client::add_marker
*/
std::vector<std::string> track_meta;
/**
* @brief Encoding buffer for opus repacketizer and encode
*/
uint8_t encode_buffer[65536];
/**
* @brief Send data to UDP socket immediately.
*
* @param data data to send
* @param length length of data to send
* @return int bytes sent. Will return -1 if we cannot send
*/
int udp_send(const char* data, size_t length);
/**
* @brief Receive data from UDP socket immediately.
*
* @param data data to receive
* @param max_length size of data receiving buffer
* @return int bytes received. -1 if there is an error
* (e.g. EAGAIN)
*/
int udp_recv(char* data, size_t max_length);
/**
* @brief This hooks the ssl_client, returning the file
* descriptor if we want to send buffered data, or
* -1 if there is nothing to send
*
* @return int file descriptor or -1
*/
dpp::socket want_write();
/**
* @brief This hooks the ssl_client, returning the file
* descriptor if we want to receive buffered data, or
* -1 if we are not wanting to receive
*
* @return int file descriptor or -1
*/
dpp::socket want_read();
/**
* @brief Called by ssl_client when the socket is ready
* for writing, at this point we pick the head item off
* the buffer and send it. So long as it doesn't error
* completely, we pop it off the head of the queue.
*/
void write_ready();
/**
* @brief Called by ssl_client when there is data to be
* read. At this point we insert that data into the
* input queue.
* @throw dpp::voice_exception if voice support is not compiled into D++
*/
void read_ready();
/**
* @brief Send data to the UDP socket, using the buffer.
*
* @param packet packet data
* @param len length of packet
* @param duration duration of opus packet
*/
void send(const char* packet, size_t len, uint64_t duration);
/**
* @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
*
*/
void clear_queue();
/**
* @brief Get the size of the outbound message queue
*
* @return The size of the queue
*/
size_t get_queue_size();
/**
* @brief Encode a byte buffer using opus codec.
* Multiple opus frames (2880 bytes each) will be encoded into one packet for sending.
*
* @param input Input data as raw bytes of PCM data
* @param inDataSize Input data length
* @param output Output data as an opus encoded packet
* @param outDataSize Output data length, should be at least equal to the input size.
* Will be adjusted on return to the actual compressed data size.
* @return size_t The compressed data size that was encoded.
* @throw dpp::voice_exception If data length to encode is invalid or voice support not compiled into D++
*/
size_t encode(uint8_t *input, size_t inDataSize, uint8_t *output, size_t &outDataSize);
public:
/**
* @brief Owning cluster
*/
class dpp::cluster* creator;
/**
* @brief This needs to be static, we only initialise libsodium once per program start,
* so initialising it on first use in a voice connection is best.
*/
static bool sodium_initialised;
/**
* @brief True when the thread is shutting down
*/
bool terminating;
/**
* @brief The gain value for the end of the current voice iteration.
*/
float end_gain;
/**
* @brief The gain value for the current voice iteration.
*/
float current_gain;
/**
* @brief The amount to increment each successive sample for, for the current voice iteration.
*/
float increment;
/**
* @brief Heartbeat interval for sending heartbeat keepalive
*/
uint32_t heartbeat_interval;
/**
* @brief Last voice channel websocket heartbeat
*/
time_t last_heartbeat;
/**
* @brief Thread ID
*/
std::thread::native_handle_type thread_id;
/**
* @brief Discord voice session token
*/
std::string token;
/**
* @brief Discord voice session id
*/
std::string sessionid;
/**
* @brief Server ID
*/
snowflake server_id;
/**
* @brief Moving averager.
*/
moving_averager moving_average;
/**
* @brief Channel ID
*/
snowflake channel_id;
/**
* @brief The audio type to be sent.
*
* @note On Windows, the default type is overlap audio.
* On all other platforms, it is recorded audio.
*
* If the audio is recorded, the sending of audio packets is throttled.
* Otherwise, if the audio is live, the sending is not throttled.
*
* Discord voice engine is expecting audio data as if they were from
* some audio device, e.g. microphone, where the data become available
* as they get captured from the audio device.
*
* In case of recorded audio, unlike from a device, the audio data are
* usually instantly available in large chunks. Throttling is needed to
* simulate audio data coming from an audio device. In case of live audio,
* the throttling is by nature, so no extra throttling is needed.
*
* Using live audio mode for recorded audio can cause Discord to skip
* audio data because Discord does not expect to receive, say, 3 minutes'
* worth of audio data in 1 second.
*
* There are some inaccuracies in the throttling method used by the recorded
* audio mode on some systems (mainly Windows) which causes gaps and stutters
* in the resulting audio stream. The overlap audio mode provides a different
* implementation that fixes the issue. This method is slightly more CPU
* intensive, and should only be used if you encounter issues with recorded audio
* on your system.
*
* Use discord_voice_client::set_send_audio_type to change this value as
* it ensures thread safety.
*/
enum send_audio_type_t
{
satype_recorded_audio,
satype_live_audio,
satype_overlap_audio
} send_audio_type =
#ifdef _WIN32
satype_overlap_audio;
#else
satype_recorded_audio;
#endif
/**
* @brief Sets the gain for the specified user.
*
* Similar to the User Volume slider, controls the listening volume per user.
* Uses native Opus gain control, so clients don't have to perform extra
* audio processing.
*
* The gain setting will affect the both individual and combined voice audio.
*
* The gain value can also be set even before the user connects to the voice
* channel.
*
* @param user_id The ID of the user where the gain is to be controlled.
* @param factor Nonnegative factor to scale the amplitude by, where 1.f reverts
* to the default volume.
*/
void set_user_gain(snowflake user_id, float factor);
/**
* @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;
/**
* @brief Fires every second from the underlying socket I/O loop, used for sending heartbeats
* @throw dpp::exception if the socket needs to disconnect
*/
virtual void one_second_timer();
/**
* @brief voice client is ready to stream audio.
* The voice client is considered ready if it has a secret key.
*
* @return true if ready to stream audio
*/
bool is_ready();
/**
* @brief Returns true if the voice client is connected to the websocket
*
* @return True if connected
*/
bool is_connected();
/**
* @brief Returns the connection time of the voice client
*
* @return dpp::utility::uptime Detail of how long the voice client has been connected for
*/
dpp::utility::uptime get_uptime();
/**
* @brief The time (in milliseconds) between each interval when parsing audio.
*
* @warning You should only change this if you know what you're doing. It is set to 500ms by default.
*/
uint16_t iteration_interval{500};
/** Constructor takes shard id, max shards and token.
* @param _cluster The cluster which owns this voice connection, for related logging, REST requests etc
* @param _channel_id The channel id to identify the voice connection as
* @param _server_id The server id (guild id) to identify the voice connection as
* @param _token The voice session token to use for identifying to the websocket
* @param _session_id The voice session id to identify with
* @param _host The voice server hostname to connect to (hostname:port format)
* @throw dpp::voice_exception Sodium or Opus failed to initialise, or D++ is not compiled with voice support
*/
discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host);
/**
* @brief Destroy the discord voice client object
*/
virtual ~discord_voice_client();
/**
* @brief Handle JSON from the websocket.
* @param buffer The entire buffer content from the websocket client
* @return bool True if a frame has been handled
* @throw dpp::exception If there was an error processing the frame, or connection to UDP socket failed
*/
virtual bool handle_frame(const std::string &buffer);
/**
* @brief Handle a websocket error.
* @param errorcode The error returned from the websocket
*/
virtual void error(uint32_t errorcode);
/**
* @brief Start and monitor I/O loop
*/
void run();
/**
* @brief Send raw audio to the voice channel.
*
* You should send an audio packet of `send_audio_raw_max_length` (11520) bytes.
* Note that this function can be costly as it has to opus encode
* the PCM audio on the fly, and also encrypt it with libsodium.
*
* @note Because this function encrypts and encodes packets before
* pushing them onto the output queue, if you have a complete stream
* ready to send and know its length it is advisable to call this
* method multiple times to enqueue the entire stream audio so that
* it is all encoded at once (unless you have set use_opus to false).
* **Constantly calling this from dpp::cluster::on_voice_buffer_send
* can, and will, eat a TON of cpu!**
*
* @param audio_data Raw PCM audio data. Channels are interleaved,
* with each channel's amplitude being a 16 bit value.
*
* @warning **The audio data needs to be 48000Hz signed 16 bit audio, otherwise, the audio will come through incorrectly!**
*
* @param length The length of the audio data. The length should
* be a multiple of 4 (2x 16 bit stereo channels) with a maximum
* length of `send_audio_raw_max_length`, which is a complete opus
* frame at highest quality.
*
* Generally when you're streaming and you know there will be
* more packet to come you should always provide packet data with
* length of `send_audio_raw_max_length`.
* Silence packet will be appended if length is less than
* `send_audio_raw_max_length` as discord expects to receive such
* specific packet size. This can cause gaps in your stream resulting
* in distorted audio if you have more packet to send later on.
*
* @return discord_voice_client& Reference to self
*
* @throw dpp::voice_exception If data length is invalid or voice support not compiled into D++
*/
discord_voice_client& send_audio_raw(uint16_t* audio_data, const size_t length);
/**
* @brief Send opus packets to the voice channel
*
* Some containers such as .ogg may contain OPUS
* encoded data already. In this case, we don't need to encode the
* frames using opus here. We can bypass the codec, only applying
* libsodium to the stream.
*
* @param opus_packet Opus packets. Discord expects opus frames
* to be encoded at 48000Hz
*
* @param length The length of the audio data.
*
* @param duration Generally duration is 2.5, 5, 10, 20, 40 or 60
* if the timescale is 1000000 (1ms)
*
* @return discord_voice_client& Reference to self
*
* @note It is your responsibility to ensure that packets of data
* sent to send_audio are correctly repacketized for streaming,
* e.g. that audio frames are not too large or contain
* an incorrect format. Discord will still expect the same frequency
* and bit width of audio and the same signedness.
*
* @throw dpp::voice_exception If data length is invalid or voice support not compiled into D++
*/
discord_voice_client& send_audio_opus(uint8_t* opus_packet, const size_t length, uint64_t duration);
/**
* @brief Send opus packets to the voice channel
*
* Some containers such as .ogg may contain OPUS
* encoded data already. In this case, we don't need to encode the
* frames using opus here. We can bypass the codec, only applying
* libsodium to the stream.
*
* Duration is calculated internally
*
* @param opus_packet Opus packets. Discord expects opus frames
* to be encoded at 48000Hz
*
* @param length The length of the audio data.
*
* @return discord_voice_client& Reference to self
*
* @note It is your responsibility to ensure that packets of data
* sent to send_audio are correctly repacketized for streaming,
* e.g. that audio frames are not too large or contain
* an incorrect format. Discord will still expect the same frequency
* and bit width of audio and the same signedness.
*
* @throw dpp::voice_exception If data length is invalid or voice support not compiled into D++
*/
discord_voice_client& send_audio_opus(uint8_t* opus_packet, const size_t length);
/**
* @brief Send silence to the voice channel
*
* @param duration How long to send silence for. With the standard
* timescale this is in milliseconds. Allowed values are 2.5,
* 5, 10, 20, 40 or 60 milliseconds.
* @return discord_voice_client& Reference to self
* @throw dpp::voice_exception if voice support is not compiled into D++
*/
discord_voice_client& send_silence(const uint64_t duration);
/**
* @brief Sets the audio type that will be sent with send_audio_* methods.
*
* @see send_audio_type_t
*/
discord_voice_client& set_send_audio_type(send_audio_type_t type);
/**
* @brief Set the timescale in nanoseconds.
*
* @param new_timescale Timescale to set. This defaults to 1000000,
* which means 1 millisecond.
* @return discord_voice_client& Reference to self
* @throw dpp::voice_exception If data length is invalid or voice support not compiled into D++
*/
discord_voice_client& set_timescale(uint64_t new_timescale);
/**
* @brief Get the current timescale, this will default to 1000000
* which means 1 millisecond.
*
* @return uint64_t timescale in nanoseconds
*/
uint64_t get_timescale();
/**
* @brief Mark the voice connection as 'speaking'.
* This sends a JSON message to the voice websocket which tells discord
* that the user is speaking. The library automatically calls this for you
* whenever you send audio.
*
* @return discord_voice_client& Reference to self
*/
discord_voice_client& speak();
/**
* @brief Pause sending of audio
*
* @param pause True to pause, false to resume
* @return reference to self
*/
discord_voice_client& pause_audio(bool pause);
/**
* @brief Immediately stop all audio.
* Clears the packet queue.
* @return reference to self
*/
discord_voice_client& stop_audio();
/**
* @brief Change the iteration interval time.
*
* @param time The time (in milliseconds) between each interval when parsing audio.
*
* @return Reference to self.
*/
discord_voice_client& set_iteration_interval(uint16_t interval);
/**
* @brief Get the iteration interval time (in milliseconds).
*
* @return iteration_interval
*/
uint16_t get_iteration_interval();
/**
* @brief Returns true if we are playing audio
*
* @return true if audio is playing
*/
bool is_playing();
/**
* @brief Get the number of seconds remaining
* of the audio output buffer
*
* @return float number of seconds remaining
*/
float get_secs_remaining();
/**
* @brief Get the number of tracks remaining
* in the output buffer.
* This is calculated by the number of track
* markers plus one.
* @return uint32_t Number of tracks in the
* buffer
*/
uint32_t get_tracks_remaining();
/**
* @brief Get the time remaining to send the
* audio output buffer in hours:minutes:seconds
*
* @return dpp::utility::uptime length of buffer
*/
dpp::utility::uptime get_remaining();
/**
* @brief Insert a track marker into the audio
* output buffer.
* A track marker is an arbitrary flag in the
* buffer contents that indicates the end of some
* block of audio of significance to the sender.
* This may be a song from a streaming site, or
* some voice audio/speech, a sound effect, or
* whatever you choose. You can later skip
* to the next marker using the
* dpp::discord_voice_client::skip_to_next_marker
* function.
* @param metadata Arbitrary information related to this
* track
* @return reference to self
*/
discord_voice_client& insert_marker(const std::string& metadata = "");
/**
* @brief Skip tp the next track marker,
* previously inserted by using the
* dpp::discord_voice_client::insert_marker
* function. If there are no markers in the
* output buffer, then this skips to the end
* of the buffer and is equivalent to the
* dpp::discord_voice_client::stop_audio
* function.
* @note It is possible to use this function
* while the output stream is paused.
* @return reference to self
*/
discord_voice_client& skip_to_next_marker();
/**
* @brief Get the metadata string associated with each inserted marker.
*
* @return const std::vector<std::string>& list of metadata strings
*/
const std::vector<std::string> get_marker_metadata();
/**
* @brief Returns true if the audio is paused.
* You can unpause with
* dpp::discord_voice_client::pause_audio.
*
* @return true if paused
*/
bool is_paused();
/**
* @brief Discord external IP detection.
* @return std::string Your external IP address
* @note This is a blocking operation that waits
* for a single packet from Discord's voice servers.
*/
std::string discover_ip();
};
} // namespace dpp

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
/************************************************************************************
*
* 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>
namespace dpp {
/**
* @brief Represents a cached DNS result.
* Used by the ssl_client class to store cached copies of dns lookups.
*/
struct dns_cache_entry {
/**
* @brief Resolved address information
*/
addrinfo addr;
/**
* @brief Socket address.
* Discord only supports ipv4, but sockaddr_in6 is larger
* than sockaddr_in, sockaddr_storage will hold either. This
* means that if discord ever do support ipv6 we just flip
* one value in dns.cpp and that should be all that is needed.
*/
sockaddr_storage ai_addr;
/**
* @brief Time at which this cache entry is invalidated
*/
time_t expire_timestamp;
};
/**
* @brief Cache container type
*/
using dns_cache_t = std::unordered_map<std::string, 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
*/
const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port);
} // namespace dpp

View File

@@ -0,0 +1,76 @@
/************************************************************************************
*
* 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/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/sync.h>
#include <dpp/colors.h>
#include <dpp/discordevents.h>
#include <dpp/timed_listener.h>
#include <dpp/collector.h>

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;
} // namespace dpp

View File

@@ -0,0 +1,251 @@
/************************************************************************************
*
* 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 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(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;
} // namespace dpp

View File

@@ -0,0 +1,162 @@
/************************************************************************************
*
* 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 A subscription for a guild.
* @warning This can only be used when creating a test entitlement.
*/
GUILD_SUBSCRIPTION = 1,
/**
* @brief A subscription for a user.
* @warning This can only be used when creating a test entitlement.
*/
USER_SUBSCRIPTION = 2,
/**
* @brief Entitlement was purchased as an app subscription.
*/
APPLICATION_SUBSCRIPTION = 8
};
/**
* @brief Entitlement flags.
*/
enum entitlement_flags : uint16_t {
/**
* @brief Entitlement was deleted
*/
ent_deleted = 0b000000000000001,
};
/**
* @brief A definition of a discord entitlement.
*/
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 SKU
*/
snowflake sku_id{0};
/**
* @brief ID of the parent application
*/
snowflake application_id{0};
/**
* @brief Optional: ID of the user/guild that is granted access to the entitlement's SKU
*/
snowflake owner_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
*/
entitlement_type get_type() const;
/**
* @brief Was the entitlement deleted?
*
* @return true if the entitlement was deleted.
*/
bool is_deleted() const;
};
/**
* @brief Group of entitlements.
*/
typedef std::unordered_map<snowflake, entitlement> entitlement_map;
} // namespace dpp

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);
};
} // namespace dpp

View File

@@ -0,0 +1,157 @@
/************************************************************************************
*
* 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);
/* 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);
} // namespace dpp::events

View File

@@ -0,0 +1,767 @@
/************************************************************************************
*
* 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 {
#ifdef DPP_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)>>;
#ifdef DPP_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;
#ifdef DPP_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");
}
}
};
}
#ifdef DPP_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() {
#ifdef DPP_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 {
#ifdef DPP_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 {
#ifdef DPP_CORO
handle_coro(std::move(event));
#else
handle(std::move(event));
#endif
};
#ifdef DPP_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::job my_handler(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.
*
* Example:
* @details Example: @code{cpp}
* dpp::job my_handler(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 {
#ifdef DPP_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&)` (the latter requires DPP_CORO to be defined),
* 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&)` (the latter requires DPP_CORO to be defined),
* 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_ */
# ifdef DPP_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::job, const T&> || 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;
}
/**
* @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.
*
* @deprecated dpp::job event handlers are deprecated and will be removed in a future version, use dpp::task<void> instead.
* @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::job, const T&>)
DPP_DEPRECATED("dpp::job event handlers are deprecated and will be removed in a future version, use dpp::task<void> instead")
[[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<regular_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.
*
* @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_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);
}
};
#ifdef DPP_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
} // namespace dpp

View File

@@ -0,0 +1,605 @@
/************************************************************************************
*
* 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, sodium 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_sodium = 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 { };
#ifdef DPP_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_CORO */
#else
derived_exception(logic_exception, exception);
derived_exception(file_exception, exception);
derived_exception(connection_exception, exception);
derived_exception(voice_exception, 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);
# ifdef DPP_CORO
derived_exception(task_cancelled_exception, exception);
# endif /* DPP_CORO */
#endif
} // namespace dpp

View File

@@ -0,0 +1,137 @@
/************************************************************************************
*
* 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
#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 */
}
#ifndef _WIN32
#define SOCKET int
#else
#define NOMINMAX
#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_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,354 @@
/************************************************************************************
*
* 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/sslclient.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;
};
/**
* @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_client {
/**
* @brief Current connection state
*/
http_state state;
/**
* @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;
/**
* @brief Handle input buffer
*
* @param buffer Buffer to read
* @return returns true if the connection should remain open
*/
bool do_buffer(std::string& buffer);
protected:
/**
* @brief Start the connection
*/
virtual void connect();
/**
* @brief Get request state
* @return request state
*/
http_state get_state();
public:
/**
* @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 http_protocol Request HTTP protocol
*/
https_client(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");
/**
* @brief Destroy the https client object
*/
virtual ~https_client() = default;
/**
* @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);
/**
* @brief Close HTTPS socket
*/
virtual void close();
/**
* @brief Fires every second from the underlying socket I/O loop, used for timeouts
*/
virtual void one_second_timer();
/**
* @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);
};
} // namespace dpp

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/json_fwd.h>
#include <unordered_map>
#include <dpp/json_interface.h>
#include <dpp/user.h>
namespace dpp {
/**
* @brief Integration types
*/
enum integration_type {
/**
* @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 {
/**
* @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;
} // namespace dpp

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
};
} // namespace dpp

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;
} // namespace dpp

View File

@@ -0,0 +1,110 @@
/************************************************************************************
*
* 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>
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);
}
};
} // namespace dpp
#endif

View File

@@ -0,0 +1,113 @@
/************************************************************************************
*
* 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>
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);
}
};
} // namespace dpp
#endif

View File

@@ -0,0 +1,116 @@
/************************************************************************************
*
* 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>
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);
}
};
} // namespace dpp
#endif

View File

@@ -0,0 +1,76 @@
/************************************************************************************
*
* 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 <numeric>
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]);
}
}
};
} // namespace dpp

View File

@@ -0,0 +1,31 @@
/************************************************************************************
*
* 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 == 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

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

View File

@@ -0,0 +1,32 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
************************************************************************************/
#ifdef DPP_USE_EXTERNAL_JSON
#include <nlohmann/json_fwd.hpp>
#else
#include <dpp/nlohmann/json_fwd.hpp>
#endif
namespace dpp {
using json = nlohmann::json;
}

View File

@@ -0,0 +1,73 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <dpp/json_fwd.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();
}
};
} // namespace dpp

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;
}
};
} // namespace dpp

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
/************************************************************************************
*
* 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 <stddef.h>
namespace dpp {
/**
* @brief Supported image types for profile pictures and CDN endpoints
*/
enum image_type {
/**
* @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
};
} // namespace dpp

File diff suppressed because it is too large Load Diff

View File

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

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);
};
} // namespace dpp

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;
}
};
} // namespace dpp

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;
} // namespace dpp

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_prune_count 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
};
} // namespace dpp

View File

@@ -0,0 +1,616 @@
/************************************************************************************
*
* 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 <thread>
#include <shared_mutex>
#include <vector>
#include <functional>
#include <condition_variable>
namespace dpp {
/**
* @brief Error values. Most of these are currently unused in https_client.
*/
enum http_error {
/**
* @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. The completion events
* arrive in order.
*/
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;
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 owner creating cluster
*/
http_request_completion_t run(class cluster* owner);
/** @brief Returns true if the request is complete */
bool is_completed();
};
/**
* @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 thread in the thread pool handling requests to HTTP(S) servers.
* There are several of these, the total defined by a constant in queues.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, so that requests are less likely to block while
* waiting for internal containers to be usable.
*/
class DPP_EXPORT in_thread {
private:
/**
* @brief True if ending.
*/
bool terminating;
/**
* @brief Request queue that owns this in_thread.
*/
class request_queue* requests;
/**
* @brief The cluster that owns this in_thread.
*/
class cluster* creator;
/**
* @brief Inbound queue mutex thread safety.
*/
std::shared_mutex in_mutex;
/**
* @brief Inbound queue thread.
*/
std::thread* in_thr;
/**
* @brief Inbound queue condition, signalled when there are requests to fulfill.
*/
std::condition_variable in_ready;
/**
* @brief Rate-limit bucket counters.
*/
std::map<std::string, bucket_t> buckets;
/**
* @brief Queue of requests to be made.
*/
std::map<std::string, std::vector<http_request*>> requests_in;
/**
* @brief Inbound queue thread loop.
* @param index Thread index
*/
void in_loop(uint32_t index);
public:
/**
* @brief Construct a new in thread object
*
* @param owner Owning cluster
* @param req_q Owning request queue
* @param index Thread index number
*/
in_thread(class cluster* owner, class request_queue* req_q, uint32_t index);
/**
* @brief Destroy the in thread object
* This will end the thread that is owned by this object by joining it.
*/
~in_thread();
/**
* @brief Post a http_request to this thread.
*
* @param req http_request to post. The pointer will be freed when it has
* been executed.
*/
void post_request(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 two threads, one to make outbound HTTP requests and push the returned
* results into a queue, and the second to call the callback methods with these results.
* They are separated so that 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().
*/
class DPP_EXPORT request_queue {
protected:
/**
* @brief Required so in_thread can access these member variables
*/
friend class in_thread;
/**
* @brief The cluster that owns this request_queue
*/
class cluster* creator;
/**
* @brief Outbound queue mutex thread safety
*/
std::shared_mutex out_mutex;
/**
* @brief Outbound queue thread
* Note that although there are many 'in queues', which handle the HTTP requests,
* there is only ever one 'out queue' which dispatches the results to the caller.
* This is to simplify thread management in bots that use the library, as less mutexing
* and thread safety boilerplate is required.
*/
std::thread* out_thread;
/**
* @brief Outbound queue condition.
* Signalled when there are requests completed to call callbacks for.
*/
std::condition_variable out_ready;
/**
* @brief Completed requests queue
*/
std::queue<std::pair<http_request_completion_t*, http_request*>> responses_out;
/**
* @brief A vector of inbound request threads forming a pool.
* There are a set number of these defined by a constant in queues.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 thread 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 threads in the pool. These are few and far between.
*/
std::vector<in_thread*> requests_in;
/**
* @brief Completed requests to delete
*/
std::multimap<time_t, std::pair<http_request_completion_t*, http_request*>> responses_to_delete;
/**
* @brief Set to true if the threads should terminate
*/
bool terminating;
/**
* @brief True if globally rate limited - makes the entire request thread wait
*/
bool globally_ratelimited;
/**
* @brief How many seconds we are globally rate limited for
*
* @note Only if globally_ratelimited is true.
*/
uint64_t globally_limited_for;
/**
* @brief Number of request threads in the thread pool
*/
uint32_t in_thread_pool_size;
/**
* @brief Outbound queue thread loop
*/
void out_loop();
public:
/**
* @brief constructor
* @param owner The creating cluster.
* @param request_threads The number of http request threads to allocate to the threadpool.
* By default eight threads are allocated.
* Side effects: Creates threads for the queue
*/
request_queue(class cluster* owner, uint32_t request_threads = 8);
/**
* @brief Add more request threads to the library at runtime.
* @note You should do this at a quiet time when there are few requests happening.
* This will reorganise the hashing used to place requests into the thread pool so if you do
* this while the bot is busy there is a small chance of receiving "429 rate limited" errors.
* @param request_threads Number of threads to add. It is not possible to scale down at runtime.
* @return reference to self
*/
request_queue& add_request_threads(uint32_t request_threads);
/**
* @brief Get the request thread count
* @return uint32_t number of request threads that are active
*/
uint32_t get_request_thread_count() const;
/**
* @brief Destroy the request queue object.
* Side effects: Joins and deletes queue threads
*/
~request_queue();
/**
* @brief Put a http_request into the request queue. You should ALWAYS "new" an object
* to pass to here -- don't submit an object that's on the stack!
* @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(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;
};
} // namespace dpp

View File

@@ -0,0 +1,264 @@
/************************************************************************************
*
* 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, 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));
}
});
}
} // namespace dpp

View File

@@ -0,0 +1,337 @@
/************************************************************************************
*
* 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 <dpp/sync.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 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,
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
*/
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;
} // namespace dpp

View File

@@ -0,0 +1,997 @@
/************************************************************************************
*
* 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 <variant>
#include <dpp/export.h>
#include <dpp/managed.h>
#include <dpp/json_fwd.h>
#include <dpp/permissions.h>
#include <dpp/guild.h>
#include <dpp/json_interface.h>
namespace dpp {
/**
* @brief Various flags related to dpp::role
*/
enum role_flags : uint8_t {
/**
* @brief Hoisted role (if the role is pinned in the user listing).
*/
r_hoist = 0b00000001,
/**
* @brief Managed role (introduced by a bot or application).
*/
r_managed = 0b00000010,
/**
* @brief Whether this role is mentionable with a ping.
*/
r_mentionable = 0b00000100,
/**
* @brief Whether this is the guild's booster role.
*/
r_premium_subscriber = 0b00001000,
/**
* @brief Whether the role is available for purchase.
*/
r_available_for_purchase = 0b00010000,
/**
* @brief Whether the role is a guild's linked role.
*/
r_guild_connections = 0b00100000,
/**
* @brief Whether the role can be selected by members in an onboarding prompt.
*/
r_in_prompt = 0b01000000,
};
/**
* @brief Represents a role within a dpp::guild.
* Roles are combined via logical OR of the permission bitmasks, then channel-specific overrides
* can be applied on top, deny types apply a logic NOT to the bit mask, and allows apply a logical OR.
*
* @note Every guild has at least one role, called the 'everyone' role, which always has the same role
* ID as the guild's ID. This is the base permission set applied to all users where no other role or override
* applies, and is the starting value of the bit mask looped through to calculate channel permissions.
*/
class DPP_EXPORT role : public managed, public json_interface<role> {
protected:
friend struct json_interface<role>;
/**
* @brief Fill this role from json.
*
* @param j The json data
* @return A reference to self
*/
role& fill_from_json_impl(nlohmann::json* j);
/**
* @brief Build a json from this object.
*
* @param with_id true if the ID is to be included in the json
* @return The json of the role
*/
virtual json to_json_impl(bool with_id = false) const;
public:
/**
* @brief Role name
* Between 1 and 100 characters.
*/
std::string name{};
/**
* @brief Guild ID
*/
snowflake guild_id{0};
/**
* @brief Role colour.
* A colour of 0 means no colour. If you want a black role,
* you must use the value 0x000001.
*/
uint32_t colour{0};
/**
* @brief Role position.
*/
uint8_t position{0};
/**
* @brief Role permissions bitmask values from dpp::permissions.
*/
permission permissions{};
/**
* @brief Role flags from dpp::role_flags
*/
uint8_t flags{0};
/**
* @brief Integration id if any.
* (e.g. role is a bot's role created when it was invited).
*/
snowflake integration_id{};
/**
* @brief Bot id if any.
* (e.g. role is a bot's role created when it was invited)
*/
snowflake bot_id{};
/**
* @brief The id of the role's subscription sku and listing.
*/
snowflake subscription_listing_id{};
/**
* @brief The unicode emoji used for the role's icon.
*
* @note This can be an empty string.
*/
std::string unicode_emoji{};
/**
* @brief The role icon.
*/
utility::icon icon{};
/**
* @brief Construct a new role object
*/
role() = default;
/**
* @brief Construct a new role object.
*
* @param rhs Role object to copy
*/
role(const role& rhs) = default;
/**
* @brief Construct a new role object.
*
* @param rhs Role object to move
*/
role(role&& rhs) = default;
/**
* @brief Copy another role object
*
* @param rhs Role object to copy
*/
role &operator=(const role& rhs) = default;
/**
* @brief Move from another role object
*
* @param rhs Role object to copy
*/
role &operator=(role&& rhs) = default;
/**
* @brief Destroy the role object
*/
virtual ~role() = default;
/**
* @brief Create a mentionable role.
* @param id The ID of the role.
* @return std::string The formatted mention of the role.
*/
static std::string get_mention(const snowflake& id);
/**
* @brief Set the name of the role.
* Maximum length: 100
* Minimum length: 1
* @param n Name to set
* @return role& reference to self
* @throw dpp::exception thrown if role length is less than 1 character
*/
role& set_name(const std::string& n);
/**
* @brief Set the colour.
*
* @param c Colour to set
* @note There is an americanised version of this method, role::set_color().
* @return role& reference to self
*/
role& set_colour(uint32_t c);
/**
* @brief Set the color.
*
* @param c Colour to set
* @note This is an alias of role::set_colour for American spelling.
* @return role& reference to self
*/
role& set_color(uint32_t c);
/**
* @brief Set the flags.
*
* @param f Flags to set from dpp::role_flags
* @return role& reference to self
*/
role& set_flags(uint8_t f);
/**
* @brief Set the integration ID.
*
* @param i Integration ID to set
* @return role& reference to self
*/
role& set_integration_id(snowflake i);
/**
* @brief Set the bot ID.
*
* @param b Bot ID to set
* @return role& reference to self
*/
role& set_bot_id(snowflake b);
/**
* @brief Set the guild ID.
*
* @param gid Guild ID to set
* @return role& reference to self
*/
role& set_guild_id(snowflake gid);
using json_interface<role>::fill_from_json;
/**
* @brief Fill this role from json.
*
* @param guild_id the guild id to place in the json
* @param j The json data
* @return A reference to self
*/
role& fill_from_json(snowflake guild_id, nlohmann::json* j);
/**
* @brief Get the mention/ping for the role.
*
* @return std::string mention
*/
std::string get_mention() const;
/**
* @brief Returns the role'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 Load a role icon.
*
* @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
*/
role& load_image(std::string_view image_blob, const image_type type);
/**
* @brief Load a role icon.
*
* @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
*/
role& load_image(const std::byte* data, uint32_t size, const image_type type);
/**
* @brief Operator less than, used for checking if a role is below another.
*
* @param lhs first role to compare
* @param rhs second role to compare
* @return true if lhs is less than rhs
*/
friend inline bool operator< (const role& lhs, const role& rhs) {
return lhs.position < rhs.position;
}
/**
* @brief Operator greater than, used for checking if a role is above another.
*
* @param lhs first role to compare
* @param rhs second role to compare
* @return true if lhs is greater than rhs
*/
friend inline bool operator> (const role& lhs, const role& rhs) {
return lhs.position > rhs.position;
}
/**
* @brief Operator equals, used for checking if a role is ranked equal to another.
*
* @param other role to compare
* @return true if is equal to other
*/
inline bool operator== (const role& other) const {
return this->position == other.position;
}
/**
* @brief Operator not equals, used for checking if a role is ranked equal to another.
*
* @param other role to compare
* @return true if is not equal to other
*/
inline bool operator!= (const role& other) const {
return this->position != other.position;
}
/**
* @brief True if the role is hoisted.
*
* @return bool Role appears separated from others in the member list
*/
bool is_hoisted() const;
/**
* @brief True if the role is mentionable.
*
* @return bool Role is mentionable
*/
bool is_mentionable() const;
/**
* @brief True if the role is managed (belongs to a bot or application).
*
* @return bool True if the role is managed (introduced for a bot or other application by Discord)
*/
bool is_managed() const;
/**
* @brief True if the role is the guild's Booster role.
*
* @return bool whether the role is the premium subscriber, AKA "boost", role for the guild
*/
bool is_premium_subscriber() const;
/**
* @brief True if the role is available for purchase.
*
* @return bool whether this role is available for purchase
*/
bool is_available_for_purchase() const;
/**
* @brief True if the role is a linked role.
*
* @return bool True if the role is a linked role
*/
bool is_linked() const;
/**
* @brief True if the role can be selected by members in an onboarding prompt.
*
* @return bool True if the role can be selected by members in an onboarding prompt
*/
bool is_selectable_in_prompt() const;
/**
* @brief True if has create instant invite permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the instant invite permission or is administrator.
*/
bool has_create_instant_invite() const;
/**
* @brief True if has the kick members permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the kick members permission or is administrator.
*/
bool has_kick_members() const;
/**
* @brief True if has the ban members permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the ban members permission or is administrator.
*/
bool has_ban_members() const;
/**
* @brief True if has the administrator permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the administrator permission or is administrator.
*/
bool has_administrator() const;
/**
* @brief True if has the manage channels permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage channels permission or is administrator.
*/
bool has_manage_channels() const;
/**
* @brief True if has the manage guild permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage guild permission or is administrator.
*/
bool has_manage_guild() const;
/**
* @brief True if has the add reactions permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the add reactions permission or is administrator.
*/
bool has_add_reactions() const;
/**
* @brief True if has the view audit log permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the view audit log permission or is administrator.
*/
bool has_view_audit_log() const;
/**
* @brief True if has the priority speaker permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the priority speaker permission or is administrator.
*/
bool has_priority_speaker() const;
/**
* @brief True if has the stream permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the stream permission or is administrator.
*/
bool has_stream() const;
/**
* @brief True if has the view channel permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the view channel permission or is administrator.
*/
bool has_view_channel() const;
/**
* @brief True if has the send messages permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the send messages permission or is administrator.
*/
bool has_send_messages() const;
/**
* @brief True if has the send TTS messages permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the send TTS messages permission or is administrator.
*/
bool has_send_tts_messages() const;
/**
* @brief True if has the manage messages permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage messages permission or is administrator.
*/
bool has_manage_messages() const;
/**
* @brief True if has the embed links permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the embed links permission or is administrator.
*/
bool has_embed_links() const;
/**
* @brief True if has the attach files permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the attach files permission or is administrator.
*/
bool has_attach_files() const;
/**
* @brief True if has the read message history permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the read message history permission or is administrator.
*/
bool has_read_message_history() const;
/**
* @brief True if has the mention \@everyone and \@here permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the mention \@everyone and \@here permission or is administrator.
*/
bool has_mention_everyone() const;
/**
* @brief True if has the use external emojis permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the use external emojis permission or is administrator.
*/
bool has_use_external_emojis() const;
/**
* @brief True if has the view guild insights permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the view guild insights permission or is administrator.
*/
bool has_view_guild_insights() const;
/**
* @brief True if has the connect voice permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the connect voice permission or is administrator.
*/
bool has_connect() const;
/**
* @brief True if has the speak permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the speak permission or is administrator.
*/
bool has_speak() const;
/**
* @brief True if has the mute members permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the mute members permission or is administrator.
*/
bool has_mute_members() const;
/**
* @brief True if has the deafen members permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the deafen members permission or is administrator.
*/
bool has_deafen_members() const;
/**
* @brief True if has the move members permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the move members permission or is administrator.
*/
bool has_move_members() const;
/**
* @brief True if has use voice activity detection permission
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has use voice activity detection permission or is administrator.
*/
bool has_use_vad() const;
/**
* @brief True if has the change nickname permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the change nickname permission or is administrator.
*/
bool has_change_nickname() const;
/**
* @brief True if has the manage nicknames permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage nicknames permission or is administrator.
*/
bool has_manage_nicknames() const;
/**
* @brief True if has the manage roles permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage roles permission or is administrator.
*/
bool has_manage_roles() const;
/**
* @brief True if has the manage webhooks permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage webhooks permission or is administrator.
*/
bool has_manage_webhooks() const;
/**
* @brief True if has the manage emojis and stickers permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage emojis and stickers permission or is administrator.
*/
bool has_manage_emojis_and_stickers() const;
/**
* @brief True if has the use application commands permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the use application commands permission or is administrator.
*/
bool has_use_application_commands() const;
/**
* @brief True if has the request to speak permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the request to speak permission or is administrator.
*/
bool has_request_to_speak() const;
/**
* @brief True if has the manage threads permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage threads permission or is administrator.
*/
bool has_manage_threads() const;
/**
* @brief True if has the create public threads permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the create public threads permission or is administrator.
*/
bool has_create_public_threads() const;
/**
* @brief True if has the create private threads permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the create private threads permission or is administrator.
*/
bool has_create_private_threads() const;
/**
* @brief True if has the use external stickers permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the use external stickers permission or is administrator.
*/
bool has_use_external_stickers() const;
/**
* @brief True if has the send messages in threads permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the send messages in threads permission or is administrator.
*/
bool has_send_messages_in_threads() const;
/**
* @brief True if has the start embedded activities permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the start embedded activities permission or is administrator.
*/
bool has_use_embedded_activities() const;
/**
* @brief True if has the manage events permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the manage events permission or is administrator.
*/
bool has_manage_events() const;
/**
* @brief True if has the moderate users permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the moderate users permission or is administrator.
*/
bool has_moderate_members() const;
/**
* @brief True if has the view creator monetization analytics permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the view creator monetization analytics permission or is administrator.
*/
bool has_view_creator_monetization_analytics() const;
/**
* @brief True if has the use soundboard permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the use soundboard permission or is administrator.
*/
bool has_use_soundboard() const;
/**
* @brief True if has the use external sounds permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the use external sounds permission or is administrator.
*/
bool has_use_external_sounds() const;
/**
* @brief True if has the send voice messages permission.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the send voice messages permission or is administrator.
*/
bool has_send_voice_messages() const;
/**
* @brief True if has permission to use clyde AI.
*
* @note Having the administrator permission causes this method to always return true
* Channel specific overrides may apply to permissions.
* @return bool True if user has the clyde AI permission or is administrator.
*/
bool has_use_clyde_ai() const;
/**
* @brief Get guild members who have this role.
*
* @note This method requires user/members cache to be active
* @return members_container List of members who have this role
*/
members_container get_members() const;
};
/**
* @brief Application Role Connection Metadata Type
*
* @note Each metadata type offers a comparison operation that allows guilds
* to configure role requirements based on metadata values stored by the bot.
* Bots specify a `metadata value` for each user and guilds specify the
* required `guild's configured value` within the guild role settings.
*/
enum application_role_connection_metadata_type : uint8_t {
/**
* @brief The metadata value (integer) is less than or equal to the guild's configured value (integer).
*/
rc_integer_less_than_or_equal = 1,
/**
* @brief The metadata value (integer) is greater than or equal to the guild's configured value (integer).
*/
rc_integer_greater_than_or_equal = 2,
/**
* @brief The metadata value (integer) is equal to the guild's configured value (integer).
*/
rc_integer_equal = 3,
/**
* @brief The metadata value (integer) is not equal to the guild's configured value (integer).
*/
rc_integer_not_equal = 4,
/**
* @brief The metadata value (ISO8601 string) is less than or equal to the guild's configured value (integer; days before current date).
*/
rc_datetime_less_than_or_equal = 5,
/**
* @brief The metadata value (ISO8601 string) is greater than or equal to the guild's configured value (integer; days before current date).
*/
rc_datetime_greater_than_or_equal = 6,
/**
* @brief The metadata value (integer) is equal to the guild's configured value (integer; 1).
*/
rc_boolean_equal = 7,
/**
* @brief The metadata value (integer) is not equal to the guild's configured value (integer; 1).
*/
rc_boolean_not_equal = 8,
};
/**
* @brief Application Role Connection Metadata. Represents a role connection metadata for an dpp::application
*/
class DPP_EXPORT application_role_connection_metadata : public json_interface<application_role_connection_metadata> {
protected:
friend struct json_interface<application_role_connection_metadata>;
/** Fill this record from json.
* @param j The json to fill this record from
* @return Reference to self
*/
application_role_connection_metadata& 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 = false) const;
public:
/**
* @brief Type of metadata value.
*/
application_role_connection_metadata_type type;
/**
* @brief Dictionary key for the metadata field (must be `a-z`, `0-9`, or `_` characters; 1-50 characters).
*/
std::string key;
/**
* @brief Name of the metadata field (1-100 characters).
*/
std::string name;
/**
* @brief Translations of the name.
*/
std::map<std::string, std::string> name_localizations;
/**
* @brief Description of the metadata field (1-200 characters).
*/
std::string description;
/**
* @brief Translations of the description.
*/
std::map<std::string, std::string> description_localizations;
/**
* @brief Constructor
*/
application_role_connection_metadata();
virtual ~application_role_connection_metadata() = default;
};
/**
* @brief The application role connection that an application has attached to a user.
*/
class DPP_EXPORT application_role_connection : public json_interface<application_role_connection> {
protected:
friend struct json_interface<application_role_connection>;
/**
* @brief Fill this record from json.
* @param j The json to fill this record from
* @return Reference to self
*/
application_role_connection& 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 = false) const;
public:
/**
* @brief Optional: The vanity name of the platform a bot has connected (max 50 characters).
*/
std::string platform_name;
/**
* @brief Optional: The username on the platform a bot has connected (max 100 characters).
*/
std::string platform_username;
/**
* @brief Optional: Object mapping application role connection metadata keys to their "stringified" value (max 100 characters) for the user on the platform a bot has connected.
*/
std::variant<std::monostate, application_role_connection_metadata> metadata;
/**
* @brief Constructor
*/
application_role_connection();
virtual ~application_role_connection() = default;
};
/**
* @brief A group of roles.
*/
typedef std::unordered_map<snowflake, role> role_map;
/**
* @brief A group of dpp::application_role_connection_metadata objects.
*/
typedef std::vector<application_role_connection_metadata> application_role_connection_metadata_list;
} // namespace dpp

View File

@@ -0,0 +1,338 @@
/************************************************************************************
*
* 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 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(const std::byte* data, uint32_t size, const image_type type);
};
/**
* @brief A group of scheduled events.
*/
typedef std::unordered_map<snowflake, scheduled_event> scheduled_event_map;
/**
* @brief A group of scheduled event members.
*/
typedef std::unordered_map<snowflake, event_member> event_member_map;
} // namespace dpp

View File

@@ -0,0 +1,162 @@
/************************************************************************************
*
* 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 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 SKU id.
*/
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;
} // namespace dpp

View File

@@ -0,0 +1,283 @@
/************************************************************************************
*
* 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>
/**
* @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;
}
};
} // namespace dpp
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);
}
};

View File

@@ -0,0 +1,30 @@
#pragma once
#ifndef _WIN32
#ifndef SOCKET
#define SOCKET int
#endif
#endif
namespace dpp
{
/**
* @brief Represents a socket file descriptor.
* This is used to ensure parity between windows and unix-like systems.
*/
typedef SOCKET socket;
} // namespace dpp
#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

View File

@@ -0,0 +1,259 @@
/************************************************************************************
*
* 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 <dpp/socket.h>
#include <cstdint>
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 A callback for socket status
*/
typedef std::function<dpp::socket()> socket_callback_t;
/**
* @brief A socket notification callback
*/
typedef std::function<void()> socket_notification_t;
/**
* @brief Close a socket
*
* @param sfd Socket to close
* @return false on error, true on success
*/
bool close_socket(dpp::socket sfd);
/**
* @brief Set a socket to blocking or non-blocking IO
*
* @param sockfd socket to act upon
* @return false on error, true on success
*/
bool set_nonblocking(dpp::socket sockfd, bool non_blocking);
/**
* @brief Implements a simple non-blocking SSL stream client.
*
* @note although the design is non-blocking the run() method will
* execute in an infinite loop until the socket disconnects. This is intended
* to be run within a std::thread.
*/
class DPP_EXPORT ssl_client
{
private:
/**
* @brief Clean up resources
*/
void cleanup();
protected:
/**
* @brief Input buffer received from socket
*/
std::string buffer;
/**
* @brief Output buffer for sending to socket
*/
std::string obuffer;
/**
* @brief True if in nonblocking mode. The socket switches to nonblocking mode
* once ReadLoop is called.
*/
bool nonblocking;
/**
* @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 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 we are establishing a new connection, false if otherwise.
*/
bool make_new;
/**
* @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();
public:
/**
* @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 Get SSL cipher name
* @return std::string ssl cipher name
*/
std::string get_cipher();
/**
* @brief Attaching an additional file descriptor to this function will send notifications when there is data to read.
*
* NOTE: Only hook this if you NEED it as it can increase CPU usage of the thread!
* Returning -1 means that you don't want to be notified.
*/
socket_callback_t custom_readable_fd;
/**
* @brief Attaching an additional file descriptor to this function will send notifications when you are able to write
* to the socket.
*
* NOTE: Only hook this if you NEED it as it can increase CPU usage of the thread! You should toggle this
* to -1 when you do not have anything to write otherwise it'll keep triggering repeatedly (it is level triggered).
*/
socket_callback_t custom_writeable_fd;
/**
* @brief This event will be called when you can read from the custom fd
*/
socket_notification_t custom_readable_ready;
/**
* @brief This event will be called when you can write to a custom fd
*/
socket_notification_t custom_writeable_ready;
/**
* @brief True if we are keeping the connection alive after it has finished
*/
bool keepalive;
/**
* @brief Connect to a specified host and port. Throws std::runtime_error on fatal error.
* @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_client(const std::string &_hostname, const std::string &_port = "443", bool plaintext_downgrade = false, bool reuse = false);
/**
* @brief Nonblocking I/O loop
* @throw std::exception Any std::exception (or derivative) thrown from read_loop() causes reconnection of the shard
*/
void read_loop();
/**
* @brief Destroy the ssl_client object
*/
virtual ~ssl_client();
/**
* @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.
*/
virtual void write(const std::string &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;
};
} // namespace dpp

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;
} // namespace dpp

View File

@@ -0,0 +1,220 @@
/************************************************************************************
*
* 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)
{
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();
}
} // namespace dpp

View File

@@ -0,0 +1,81 @@
/************************************************************************************
*
* 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 <future>
#include <utility>
#include <dpp/exception.h>
namespace dpp {
/**
* @brief Call a D++ REST function synchronously.
*
* Synchronously calling a REST function means *IT WILL BLOCK* - This is a Bad Thing™ and strongly discouraged.
* There are very few circumstances you actually need this. If you do need to use this, you'll know it.
*
* Example:
*
* ```cpp
* dpp::message m = dpp::sync<dpp::message>(&bot, &dpp::cluster::message_create, dpp::message(channel_id, "moo."));
* ```
*
* @warning As previously mentioned, this template will block. It is ill-advised to call this outside of
* a separate thread and this should never be directly used in any event such as dpp::cluster::on_interaction_create!
* @tparam T type of expected return value, should match up with the method called
* @tparam F Type of class method in dpp::cluster to call.
* @tparam Ts Function parameters in method call
* @param c A pointer to dpp::cluster object
* @param func pointer to class method in dpp::cluster to call. This can call any
* dpp::cluster member function who's last parameter is a dpp::command_completion_event_t callback type.
* @param args Zero or more arguments for the method call
* @return An instantiated object of type T
* @throw dpp::rest_exception On failure of the method call, an exception is thrown
*/
template<typename T, class F, class... Ts> T sync(class cluster* c, F func, Ts&&... args) {
std::promise<T> _p;
std::future<T> _f = _p.get_future();
/* (obj ->* func) is the obscure syntax for calling a method pointer on an object instance */
(c ->* func)(std::forward<Ts>(args)..., [&_p](const auto& cc) {
try {
if (cc.is_error()) {
const auto& error = cc.get_error();
throw dpp::rest_exception((exception_error_code)error.code, error.message);
} else {
try {
_p.set_value(std::get<T>(cc.value));
} catch (const std::exception& e) {
throw dpp::rest_exception(err_unknown, e.what());
}
}
} catch (const dpp::rest_exception&) {
_p.set_exception(std::current_exception());
}
});
/* Blocking calling thread until rest request is completed.
* Exceptions encountered on the other thread are re-thrown.
*/
return _f.get();
}
} // namespace dpp

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)

View File

@@ -0,0 +1,230 @@
/************************************************************************************
*
* 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 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>;
}

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);
}
};
} // namespace dpp

View File

@@ -0,0 +1,132 @@
/************************************************************************************
*
* 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 <stdint.h>
#include <map>
#include <unordered_map>
#include <stddef.h>
#include <ctime>
#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;
/**
* @brief Next timer tick as unix epoch
*/
time_t next_tick;
/**
* @brief Frequency between ticks
*/
uint64_t frequency;
/**
* @brief Lambda to call on tick
*/
timer_callback_t on_tick;
/**
* @brief Lambda to call on stop (optional)
*/
timer_callback_t on_stop;
};
/**
* @brief A map of timers, ordered by earliest first so that map::begin() is always the
* soonest to be due.
*/
typedef std::multimap<time_t, timer_t*> timer_next_t;
/**
* @brief A map of timers stored by handle
*/
typedef std::unordered_map<timer, timer_t*> timer_reg_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();
};
} // namespace dpp

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,582 @@
/************************************************************************************
*
* 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 {
/**
* @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;
} // namespace dpp

File diff suppressed because it is too large Load Diff

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
#if !defined(DPP_VERSION_LONG)
#define DPP_VERSION_LONG 0x00100029
#define DPP_VERSION_SHORT 100029
#define DPP_VERSION_TEXT "D++ 10.0.29 (05-Nov-2023)"
#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

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;
} // namespace dpp

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.
*/
class discord_client* shard;
/**
* @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;
} // namespace dpp

View File

@@ -0,0 +1,199 @@
/************************************************************************************
*
* 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 may be empty.
*/
utility::iconhash avatar;
/**
* @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;
} // namespace dpp

View File

@@ -0,0 +1,33 @@
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
************************************************************************************/
#pragma once
/* This file contains pragmas to disable warnings on win32 builds with msvc only.
* It is only included during build of D++ itself, and not when including the headers
* into a user's project.
*
* Before adding a warning here please be ABSOLUTELY SURE it is one we cannot easily fix
* and is to be silenced, thrown into the sarlacc pit to be eaten for 1000 years...
*/
_Pragma("warning( disable : 4251 )"); // 4251 warns when we export classes or structures with stl member variables
_Pragma("warning( disable : 5105 )"); // 5105 is to do with macro warnings

View File

@@ -0,0 +1,235 @@
/************************************************************************************
*
* 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 <variant>
#include <dpp/sslclient.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 Implements a websocket client based on the SSL client
*/
class DPP_EXPORT websocket_client : public ssl_client {
/**
* @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 Unpack a frame and pass completed frames up the stack.
* @param buffer The buffer to operate on. Gets modified to remove completed frames on the head of the buffer
* @param offset The offset to start at (reserved for future use)
* @param first True if is the first element (reserved for future use)
* @return true if a complete frame has been received
*/
bool unpack(std::string &buffer, uint32_t offset, bool first = true);
/**
* @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 and pong requests.
* @param ping True if this is a ping, false if it is a pong
* @param payload The ping payload, to be returned as-is for a ping
*/
void handle_ping_pong(bool ping, const std::string &payload);
protected:
/**
* @brief (Re)connect
*/
virtual void connect();
/**
* @brief Get websocket state
* @return websocket state
*/
ws_state get_state();
public:
/**
* @brief Connect to a specific websocket server.
* @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 Voice websockets only support OP_TEXT, and other websockets must be
* OP_BINARY if you are going to send ETF.
*/
websocket_client(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.
*/
virtual void write(const std::string &data);
/**
* @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);
/**
* @brief Close websocket
*/
virtual void close();
/**
* @brief Receives raw frame content only without headers
*
* @param buffer The buffer contents
* @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);
/**
* @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();
/**
* @brief Send OP_CLOSE error code 1000 to the other side of the connection.
* This indicates graceful close.
*/
void send_close_packet();
};
} // namespace dpp