mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 08:47:17 +01:00
526 lines
13 KiB
C++
526 lines
13 KiB
C++
/************************************************************************************
|
|
*
|
|
* 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 <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>
|
|
|
|
using json = nlohmann::json;
|
|
|
|
#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, 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);
|
|
};
|
|
|
|
};
|