/** * @file discord.h * @author Cogmasters * @brief Public functions and datatypes * * These symbols are organized in a intuitive fashion to be easily * matched to the official Discord API docs * @see https://discord.com/developers/docs/intro */ #ifndef DISCORD_H #define DISCORD_H #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #include #include #include "concord-error.h" #include "types.h" #include "io_poller.h" #define LOGMOD_HEADER #include "logmod.h" #ifndef DISCORD_VERSION /** * @brief The Discord API version to use * @warning only change this if you know what you are doing! */ #define DISCORD_VERSION "10" #endif #define DISCORD_API_BASE_URL "https://discord.com/api/v" DISCORD_VERSION #define DISCORD_GATEWAY_URL_SUFFIX "?v=" DISCORD_VERSION "&encoding=json" /* forward declaration */ struct discord; struct reflectc; /**/ /** * @brief Get the reflectc registry from a Discord client * * @param client the client created with discord_from_json() or discord_from_token() * @return the reflectc registry used by the client */ struct reflectc *discord_get_registry(struct discord *client); #include "discord_codecs.h" #include "discord-response.h" /** @defgroup DiscordClient Client */ /** @defgroup DiscordConstants Constants * @brief Macros for constants defined by Discord * @note macros assume the worst-case scenario for strings, where each * character is 4 bytes long (UTF8) * @{ */ /** @defgroup DiscordConstantsGeneral General lengths * @brief Max length for general fields * @{ */ #define DISCORD_MAX_NAME_LEN 4 * 100 + 1 #define DISCORD_MAX_TOPIC_LEN 4 * 1024 + 1 #define DISCORD_MAX_DESCRIPTION_LEN 4 * 2048 + 1 #define DISCORD_MAX_USERNAME_LEN 4 * 32 + 1 #define DISCORD_MAX_DISCRIMINATOR_LEN 4 + 1 #define DISCORD_MAX_REASON_LEN 4 * 512 + 1 #define DISCORD_MAX_MESSAGE_LEN 4 * 2000 + 1 #define DISCORD_MAX_PAYLOAD_LEN 4 * 4096 + 1 /** @} DiscordConstantsGeneral */ /** @defgroup DiscordConstantsEmbed Embed lengths * @brief Max length for embed fields * @{ */ #define DISCORD_EMBED_TITLE_LEN 4 * 256 + 1 #define DISCORD_EMBED_DESCRIPTION_LEN 4 * 4096 + 1 #define DISCORD_EMBED_MAX_FIELDS 25 #define DISCORD_EMBED_FIELD_NAME_LEN 4 * 256 + 1 #define DISCORD_EMBED_FIELD_VALUE_LEN 4 * 1024 + 1 #define DISCORD_EMBED_FOOTER_TEXT_LEN 4 * 2048 + 1 #define DISCORD_EMBED_AUTHOR_NAME_LEN 4 * 256 + 1 /** @} DiscordConstantsEmbed */ /** @defgroup DiscordConstantsWebhook Webhook lengths * @brief Max length for embed fields * @{ */ #define DISCORD_WEBHOOK_NAME_LEN 4 * 80 + 1 /** @} DiscordConstantsWebhook */ /** @} DiscordConstants */ /** @addtogroup ConcordError * @{ */ /* XXX: As new values are added, discord_strerror() and * discord_code_as_string() should be updated accordingly! */ /** @defgroup DiscordError Discord error codes * @brief Error codes triggered from Discord * @{ */ /** Alias for @ref CCORD_OK */ #define CCORD_DISCORD_OK CCORD_OK /** action is pending (ex: request has been enqueued and will be performed * later) */ #define CCORD_PENDING 1 /** received a JSON error message */ #define CCORD_DISCORD_JSON_CODE 100 /** bad authentication token */ #define CCORD_DISCORD_BAD_AUTH 101 /** being ratelimited */ #define CCORD_DISCORD_RATELIMIT 102 /** couldn't establish connection to Discord */ #define CCORD_DISCORD_CONNECTION 103 /** * @brief Return the value of CCORDcode as a string * * @param code the CCORDcode value * @return the enum value as a string */ const char *discord_code_as_string(CCORDcode code); /** * @brief Return the meaning of CCORDcode * * @param code the CCORDcode value * @param client @note unused parameter * @return a string containing the code meaning */ const char *discord_strerror(CCORDcode code, struct discord *client); /** @} DiscordError */ /** @} ConcordError */ /** @defgroup DiscordAPI API * @brief The Discord public API supported by Concord * @{ */ #include "audit_log.h" #include "auto_moderation.h" #include "invite.h" #include "channel.h" #include "emoji.h" #include "guild.h" #include "guild_scheduled_event.h" #include "guild_template.h" #include "stage_instance.h" #include "sticker.h" #include "user.h" #include "voice.h" #include "webhook.h" #include "gateway.h" #include "oauth2.h" /** @defgroup DiscordAPIInteractions Interactions * @brief Interactions public API supported by Concord * @{ */ #include "application_command.h" #include "interaction.h" /** @} DiscordAPIInteractions */ /** @} DiscordAPI */ /** @addtogroup DiscordClient * @brief Client functions and datatypes * @{ */ /** @struct discord */ #include "discord-cache.h" #include "discord-events.h" /** * @brief Claim ownership of a resource provided by Concord * @see discord_unclaim() * * @param client the client initialized with discord_from_token() * @param data a resource provided by Concord * @return pointer to `data` (for one-liners) */ #define discord_claim(client, data) (__discord_claim(client, data), data) void __discord_claim(struct discord *client, const void *data); /** * @brief Unclaim ownership of a resource provided by Concord * @note this will make the resource eligible for cleanup, so this should * only be called when you no longer plan to use it * @see discord_claim() * * @param client the client initialized with discord_from_token() * @param data a resource provided by Concord, that has been * previously claimed with discord_claim() */ void discord_unclaim(struct discord *client, const void *data); /** @deprecated since v3.0.0, keep backwards compatibility */ #define ccord_global_init() /** @deprecated since v3.0.0, keep backwards compatibility */ #define ccord_global_cleanup() /** * @brief Gracefully notify all Discord connections for shutting down * * @note this function will not wait before returning, and will * return immediately. The shutdown process will be handled * in the background. */ void discord_shutdown_all(void); /** * @brief Check if all Discord connections shutting down is in progress * * @return true if all shutdown is in progress, false otherwise */ bool discord_shutdown_all_ongoing(void); /** * @brief Backwards compatible alias for discord_shutdown_all() * @deprecated since v3.0.0 */ #define ccord_shutdown_async() discord_shutdown_all() /** * @brief Creates a Discord Client handle from a token * @see discord_get_logmod() to configure logging behavior * * @param token the bot token * @return the newly created Discord Client handle */ struct discord *discord_from_token(const char token[]); /** * @brief Creates a Discord Client handle from a `config.json` file * @see discord_get_logmod() to configure logging behavior * * @param config_file the `config.json` file name * @return the newly created Discord Client handle */ struct discord *discord_from_json(const char config_file[]); /** * @brief The Discord configuration handler * * This struct is used to store the Discord client configuration */ struct discord_config { /** the bot token */ char *token; /** * optional override for the REST API base URL * (e.g. `"http://127.0.0.1:8080"` for a local test server) * @note when `NULL` (the default) @ref DISCORD_API_BASE_URL is used * @note only read during client initialization and copied by the * User-Agent layer; the client does not take ownership and will * never free this pointer */ char *base_url; struct { /** minimum logging level */ enum logmod_levels level; /** silence terminal logging */ bool quiet; /** enable color to terminal logging */ bool color; /** overwrite existing files */ bool overwrite; /* the trace log file */ FILE *trace; /* the http log file */ FILE *http; /* the ws log file */ FILE *ws; struct { size_t size; char **ids; } disable; /**< list of 'id' that should be ignored */ } log; /**< logging directives */ }; /** * @brief Creates a Discord Client handle from a * @ref discord_config structure * @see discord_get_logmod() to configure logging behavior * * @param config the @ref discord_config structure * @return the newly created Discord Client handle */ struct discord *discord_from_config(const struct discord_config *config); /** * @brief Backwards compatible alias for discord_from_token() * @deprecated since v3.0.0 */ #define discord_init discord_from_token /** * @brief Backwards compatible alias for discord_from_json() * @deprecated since v3.0.0 */ #define discord_config_init discord_from_json /** * @brief Get the contents from the config file field * @note your bot **MUST** have been initialized with discord_from_json() * * @code{.c} * // Assume the following custom config.json field to be extracted * // "field": { "foo": "a string", "bar": 1234 } * * ... * struct ccord_szbuf_readonly value; * char foo[128]; * long bar; * * // field.foo * value = discord_config_get_field(client, (char *[2]){ "field", "foo" }, 2); * snprintf(foo, sizeof(foo), "%.*s", (int)value.size, value.start); * // field.bar * value = discord_config_get_field(client, (char *[2]){ "field", "bar" }, 2); * bar = strtol(value.start, NULL, 10); * * printf("%s %ld", foo, bar); // "a string" 1234 * @endcode * * @param client the client created with discord_from_json() * @param path the JSON key path * @param depth the path depth * @return a read-only sized buffer containing the field's contents */ struct ccord_szbuf_readonly discord_config_get_field(struct discord *client, char *const path[], unsigned depth); /** * @brief Clone a discord client * * Should be called before entering a thread, to ensure each thread * has its own client instance with unique buffers, url and headers * @param orig the original client created with discord_from_token() * @return the client clone */ struct discord *discord_clone(const struct discord *orig); /** * @brief Free a Discord Client handle * * @param client the client created with discord_from_token() */ void discord_cleanup(struct discord *client); /** * @brief Get the client's cached user * * @param client the client created with discord_from_token() * @warning the returned structure should NOT be modified */ const struct discord_user *discord_get_self(struct discord *client); /** * @brief Start a connection to the Discord Gateway * * @param client the client created with discord_from_token() * @CCORD_return */ CCORDcode discord_run(struct discord *client); /** * @brief Gracefully shutdown an ongoing Discord connection * * @param client the client created with discord_from_token() */ void discord_shutdown(struct discord *client); /** * @brief Gracefully reconnects an ongoing Discord connection * * @param client the client created with discord_from_token() * @param resume true to attempt to resume to previous session, * false restart a fresh session */ void discord_reconnect(struct discord *client, bool resume); /** * @brief Store user arbitrary data that can be retrieved by discord_get_data() * * @param client the client created with discord_from_token() * @param data user arbitrary data * @return pointer to user data * @warning the user should provide their own locking mechanism to protect * its data from race conditions */ void *discord_set_data(struct discord *client, void *data); /** * @brief Receive user arbitrary data stored with discord_set_data() * * @param client the client created with discord_from_token() * @return pointer to user data * @warning the user should provide their own locking mechanism to protect * its data from race conditions */ void *discord_get_data(struct discord *client); /** * @brief Get the client WebSockets ping * @note Only works after a connection has been established via * discord_run() * * @param client the client created with discord_from_token() * @return the ping in milliseconds */ int discord_get_ping(struct discord *client); /** * @brief Get the current timestamp (in milliseconds) * * @param client the client created with discord_from_token() * @return the timestamp in milliseconds */ uint64_t discord_timestamp(struct discord *client); /** * @brief Get the current timestamp (in microseconds) * * @param client the client created with discord_from_token() * @return the timestamp in microseconds */ uint64_t discord_timestamp_us(struct discord *client); /** * @brief Retrieve client's logging module for configuration purposes * @see logmod.h * * @param client the client created with discord_from_token() * @return the client's logging manager */ struct logmod *discord_get_logmod(struct discord *client); /** * @brief get the io_poller used by the discord client * * @param client the client created with discord_from_token() * @return struct io_poller* */ struct io_poller *discord_get_io_poller(struct discord *client); /** @addtogroup DiscordDataWrap Data Wrap * @brief Helpers for wrapping Discord data types for easier management * @{ */ #define __CAT(_a, _b) _a##_b #define _CAT(_a, _b) __CAT(_a, _b) #define _EXPECT_CONTAINER__struct #define _EXPECT_CONTAINER__union #define _DISCORD_SYMBOL_WITHOUT_CONTAINER(_symbol) \ _CAT(_EXPECT_CONTAINER__, _symbol) /** * @brief The Discord data wrap structure * * This struct is used to wrap Discord data types for easier management */ #define discord_data_wrap reflectc_wrap /** * @brief Wrap a Discord data type into a reflectc_wrap structure * * @param _symbol the Discord data type symbol (with struct or union) * @param _client the client created with discord_from_token() * @param _data the Discord data type to be wrapped * @return the @ref discord_data_wrap Discord data type */ #define discord_data_wrap_from(_symbol, _client, _data) \ _CAT(reflectc_from_, _DISCORD_SYMBOL_WITHOUT_CONTAINER(_symbol))( \ discord_get_registry((_client)), _data, NULL) /** * @brief Cleanup a Discord data type wrapped into a reflectc_wrap structure * * Releases everything discord_data_from_json() allocated for `_data` — * strings, nested objects, list arrays — plus the reflect-c wrap and its * registry entry. Every non-NULL pointer member is treated as owned: do * not call this on hand-built structs pointing at literals or stack * objects. * * @param _client the client created with discord_from_token() * @param _data the Discord data type to be cleaned up */ #define discord_data_cleanup(_client, _data) \ discord_data_free(discord_get_registry((_client)), (_data)) /** Function backing @ref discord_data_cleanup; see its contract */ void discord_data_free(struct reflectc *registry, void *data); /** * @brief Drop the wrap cached for `_data` without touching its contents * * discord_data_to_json() caches a reflect-c wrap keyed by `_data`'s * address; release it with this once done encoding a caller-owned * struct (e.g. stack-built params). Without it the stale registry entry * would be wrongly reused by a future object at the same address. * Decoded structs don't need this — discord_data_cleanup() releases * both the data and the wrap. * * @param _client the client created with discord_from_token() * @param _data the encoded Discord data type to drop the wrap for */ #define discord_data_unwrap(_client, _data) \ discord_data_release(discord_get_registry((_client)), (_data)) /** Function backing @ref discord_data_unwrap */ void discord_data_release(struct reflectc *registry, void *data); /** @addtogroup DiscordDataWrapJSON JSON Conversion * @brief Helpers for converting Discord data types to/from JSON * @{ */ /* forward declaration */ struct jsmnf_pair; struct jsonb; /** * @brief Transform a wrapped Discord data type into a JSON string * * @param member the wrapped Discord data type * @param p_buf pointer to the JSON buffer * @param p_bufsize pointer to the JSON buffer size * @CCORD_return */ CCORDcode discord_data_wrap_to_json(const struct discord_data_wrap *member, char *p_buf[], size_t *p_bufsize); /** * @brief Transform a Discord data type into a JSON string * * @param _symbol the Discord data type symbol (with struct or union) * @param _client the client created with discord_from_token() * @param _data the Discord data type to be transformed * @param _p_buf pointer to the JSON buffer * @param _p_bufsize pointer to the JSON buffer size * @CCORD_return */ #define discord_data_to_json(_symbol, _client, _data, _p_buf, _p_bufsize) \ discord_data_wrap_to_json( \ discord_data_wrap_from(_symbol, _client, _data), _p_buf, _p_bufsize) /** * @brief Transform a wrapped Discord data type into a jsonb handle * * @param jb the jsonb handle * @param member the wrapped Discord data type * @param p_buf pointer to the JSON buffer * @param p_bufsize pointer to the JSON buffer size * @CCORD_return */ CCORDcode discord_data_wrap_to_jsonb(struct jsonb *jb, const struct discord_data_wrap *member, char *p_buf[], size_t *p_bufsize); /** * @brief Transform a Discord data type into a jsonb handle * * @param _symbol the Discord data type symbol (with struct or union) * @param _client the client created with discord_from_token() * @param _jb the jsonb handle * @param _data the Discord data type to be transformed * @param _p_buf pointer to the JSON buffer * @param _p_bufsize pointer to the JSON buffer size * @CCORD_return */ #define discord_data_to_jsonb(_symbol, _client, _jb, _data, _p_buf, \ _p_bufsize) \ discord_data_wrap_to_jsonb( \ _jb, discord_data_wrap_from(_symbol, _client, _data), _p_buf, \ _p_bufsize) /** * @brief Parse a JSON string and fill a wrapped Discord data type * * @param json the JSON string * @param len length of @ref json * @param root the root wrapped Discord data type * @CCORD_return */ CCORDcode discord_data_wrap_from_json(const char *json, size_t len, struct discord_data_wrap *root); /** * @brief Parse a JSON string and fill a Discord data type * * @param _symbol the Discord data type symbol (with struct or union) * @param _client the client created with discord_from_token() * @param _json the JSON string * @param _len length of @ref json * @param _data the Discord data type to be filled * @CCORD_return */ #define discord_data_from_json(_symbol, _client, _json, _len, _data) \ discord_data_wrap_from_json( \ _json, _len, discord_data_wrap_from(_symbol, _client, _data)) /** * @brief Parse a jsmnf_pair and fill a wrapped Discord data type * * @param p the jsmnf_pair * @param json the JSON string * @param length length of @ref json * @param member the wrapped Discord data type * @CCORD_return */ CCORDcode discord_data_wrap_from_jsmnf(const struct jsmnf_pair *p, const char *json, size_t length, const struct discord_data_wrap *member); /** * @brief Parse a jsmnf_pair and fill a Discord data type * * @param _client the client created with discord_from_token() * @param _p the jsmnf_pair * @param _json the JSON string * @param _length length of @ref json * @param _type the Discord data type symbol (without struct/union) * @param _data the Discord data type to be filled * @CCORD_return */ #define discord_data_from_jsmnf(_client, _p, _json, _length, _type, _data) \ discord_data_wrap_from_jsmnf( \ _p, _json, _length, discord_data_wrap_from(_type, _client, _data)) /** @} DiscordDataWrapJSON */ /** @} DiscordDataWrap */ /** @addtogroup DiscordTimer Timer * @brief Schedule callbacks to be called in the future * @{ */ /* forward declaration */ struct discord_timer; /**/ /** @brief callback to be used with struct discord_timer */ typedef void (*discord_ev_timer)(struct discord *client, struct discord_timer *ev); /** @brief flags used to change behaviour of timer */ enum discord_timer_flags { /** use milliseconds for interval and start_time */ DISCORD_TIMER_MILLISECONDS = 0, /** use microseconds for interval and start_time */ DISCORD_TIMER_MICROSECONDS = 1 << 0, /** whether or not timer is marked for deletion */ DISCORD_TIMER_DELETE = 1 << 1, /** automatically delete a timer once its repeat counter runs out */ DISCORD_TIMER_DELETE_AUTO = 1 << 2, /** timer has been canceled. user should cleanup only */ DISCORD_TIMER_CANCELED = 1 << 3, /** flag is set when on_tick callback has been called */ DISCORD_TIMER_TICK = 1 << 4, /** used in discord_timer_ctl to get the timer's data */ DISCORD_TIMER_GET = 1 << 5, /** timer should run using a fixed interval based on start time */ DISCORD_TIMER_INTERVAL_FIXED = 1 << 6, }; /** @brief struct used for modifying, and getting info about a timer */ struct discord_timer { /** the identifier used for the timer. 0 creates a new timer */ unsigned id; /** the flags used to manipulate the timer */ enum discord_timer_flags flags; /** (nullable) the callback that should be called when timer triggers */ discord_ev_timer on_tick; /** (nullable) the callback for status updates timer->flags * will have: DISCORD_TIMER_CANCELED, and DISCORD_TIMER_DELETE */ discord_ev_timer on_status_changed; /** user data */ void *data; /** delay before timer should start */ int64_t delay; /** interval that the timer should repeat at. must be >= 0 */ int64_t interval; /** how many times a timer should repeat (-1 == infinity) */ int64_t repeat; }; /** * @brief modifies or creates a timer * * @param client the client created with discord_from_token() * @param timer the timer that should be modified * @return the id of the timer */ unsigned discord_timer_ctl(struct discord *client, struct discord_timer *timer); /** * @brief creates a one shot timer that automatically * deletes itself upon completion * * @param client the client created with discord_from_token() * @param on_tick_cb (nullable) the callback that should be called when timer * triggers * @param on_status_changed_cb (nullable) the callback for status updates * timer->flags will have: DISCORD_TIMER_CANCELED, and DISCORD_TIMER_DELETE * @param data user data * @param delay delay before timer should start in milliseconds * @return the id of the timer */ unsigned discord_timer(struct discord *client, discord_ev_timer on_tick_cb, discord_ev_timer on_status_changed_cb, void *data, int64_t delay); /** * @brief creates a repeating timer that automatically * deletes itself upon completion * * @param client the client created with discord_from_token() * @param on_tick_cb (nullable) the callback that should be called when timer * triggers * @param on_status_changed_cb (nullable) the callback for status updates * timer->flags will have: DISCORD_TIMER_CANCELED, and DISCORD_TIMER_DELETE * @param data user data * @param delay delay before timer should start in milliseconds * @param interval interval between runs. (-1 == disable repeat) * @param repeat repetitions (-1 == infinity) * @return the id of the timer */ unsigned discord_timer_interval(struct discord *client, discord_ev_timer on_tick_cb, discord_ev_timer on_status_changed_cb, void *data, int64_t delay, int64_t interval, int64_t repeat); /** * @brief get the data associated with the timer * * @param client the client created with discord_from_token() * @param id id of the timer * @param timer where to copy the timer data to * @return true on success */ bool discord_timer_get(struct discord *client, unsigned id, struct discord_timer *timer); /** * @brief starts a timer * * @param client the client created with discord_from_token() * @param id id of the timer * @return true on success */ bool discord_timer_start(struct discord *client, unsigned id); /** * @brief stops a timer * * @param client the client created with discord_from_token() * @param id id of the timer * @return true on success */ bool discord_timer_stop(struct discord *client, unsigned id); /** * @brief cancels a timer, * this will delete the timer if DISCORD_TIMER_DELETE_AUTO is enabled * * @param client the client created with discord_from_token() * @param id id of the timer * @return true on success */ bool discord_timer_cancel(struct discord *client, unsigned id); /** * @brief deletes a timer * * @param client the client created with discord_from_token() * @param id id of the timer * @return true on success */ bool discord_timer_delete(struct discord *client, unsigned id); /** * @brief cancels, and deletes a timer * * @param client the client created with discord_from_token() * @param id id of the timer * @return true on success */ bool discord_timer_cancel_and_delete(struct discord *client, unsigned id); /** @example timers.c * Demonstrates the Timer API for callback scheduling */ /** @} DiscordTimer */ /** @} DiscordClient */ #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* DISCORD_H */