DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
DiscordCoreClient.cpp
Go to the documentation of this file.
1/*
2 MIT License
3
4 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
5
6 Copyright 2022, 2023 Chris M. (RealTimeChris)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26/// DiscordCoreClient.cpp - Source file for the main/exposed discord_core_client class.
27/// May 12, 2021
28/// https://discordcoreapi.com
29/// \file DiscordCoreClient.cpp
30
31#include <atomic>
32#include <csignal>
35
36namespace discord_core_api {
37
38 voice_connections_map voiceConnectionMap{};
39 sound_cloud_api_map soundCloudAPIMap{};
40 you_tube_api_map youtubeAPIMap{};
41 song_api_map songAPIMap{};
42 std::atomic_bool doWeQuit{};
43
44 discord_core_internal::sound_cloud_api& discord_core_client::getSoundCloudAPI(snowflake guildId) {
45 if (!soundCloudAPIMap.contains(guildId.operator const uint64_t&())) {
46 soundCloudAPIMap[guildId.operator const uint64_t&()] = makeUnique<discord_core_internal::sound_cloud_api>(&getInstance()->configManager, guildId);
47 }
48 return *soundCloudAPIMap[guildId.operator const uint64_t&()].get();
49 }
50
51 discord_core_internal::you_tube_api& discord_core_client::getYouTubeAPI(snowflake guildId) {
52 if (!youtubeAPIMap.contains(guildId.operator const uint64_t&())) {
53 youtubeAPIMap[guildId.operator const uint64_t&()] = makeUnique<discord_core_internal::you_tube_api>(&getInstance()->configManager, guildId);
54 }
55 return *youtubeAPIMap[guildId.operator const uint64_t&()].get();
56 }
57
58 voice_connection& discord_core_client::getVoiceConnection(snowflake guildId) {
59 if (!voiceConnectionMap.contains(guildId.operator const uint64_t&())) {
60 uint64_t theShardId{ (guildId.operator const uint64_t&() >> 22) % getInstance()->configManager.getTotalShardCount() };
61 uint64_t baseSocketIndex{ theShardId % getInstance()->baseSocketAgentsMap.size() };
62 voiceConnectionMap[guildId.operator const uint64_t&()] =
63 makeUnique<voice_connection>(&getInstance()->baseSocketAgentsMap[baseSocketIndex]->shardMap[theShardId], &doWeQuit);
64 }
65 return *voiceConnectionMap[guildId.operator const uint64_t&()].get();
66 }
67
68 song_api& discord_core_client::getSongAPI(snowflake guildId) {
69 if (!songAPIMap.contains(guildId.operator const uint64_t&())) {
70 songAPIMap[guildId.operator const uint64_t&()] = makeUnique<song_api>(guildId);
71 }
72 return *songAPIMap[guildId.operator const uint64_t&()].get();
73 }
74
75 discord_core_client* discord_core_client::getInstance() {
76 return instancePtr.get();
77 ;
78 }
79
80 void atexitHandler() noexcept {
81 doWeQuit.store(true, std::memory_order_release);
82 }
83
84 void signalHandler(int32_t value) noexcept {
85 switch (value) {
86 case SIGTERM: {
88 exit(EXIT_FAILURE);
89 }
90 case SIGSEGV: {
92 exit(EXIT_FAILURE);
93 }
94 case SIGINT: {
96 exit(EXIT_SUCCESS);
97 }
98 case SIGILL: {
100 exit(EXIT_FAILURE);
101 }
102 case SIGABRT: {
104 exit(EXIT_FAILURE);
105 }
106 case SIGFPE: {
108 exit(EXIT_FAILURE);
109 }
110 }
111 }
112
113 discord_core_client::discord_core_client(const discord_core_client_config& configData) : configManager{ configData } {
114 instancePtr.reset(this);
115 std::atexit(&atexitHandler);
116 std::signal(SIGTERM, &signalHandler);
117 std::signal(SIGSEGV, &signalHandler);
118 std::signal(SIGINT, &signalHandler);
119 std::signal(SIGILL, &signalHandler);
120 std::signal(SIGABRT, &signalHandler);
121 std::signal(SIGFPE, &signalHandler);
122 message_printer::initialize(configManager);
123 if (!discord_core_internal::ssl_context_holder::initialize()) {
124 message_printer::printError<print_message_type::general>("Failed to initialize the SSL_CTX structure!");
125 return;
126 }
127 if (sodium_init() == -1) {
128 message_printer::printError<print_message_type::general>("Lib_sodium failed to initialize!");
129 return;
130 }
131 httpsClient = makeUnique<discord_core_internal::https_client>(jsonifier::string{ configManager.getBotToken() });
132 application_commands::initialize(httpsClient.get());
133 auto_moderation_rules::initialize(httpsClient.get());
134 channels::initialize(httpsClient.get(), &configManager);
135 guilds::initialize(httpsClient.get(), &configManager);
136 guild_members::initialize(httpsClient.get(), &configManager);
137 guild_scheduled_events::initialize(httpsClient.get());
138 interactions::initialize(httpsClient.get());
139 messages::initialize(httpsClient.get());
140 reactions::initialize(httpsClient.get());
141 roles::initialize(httpsClient.get(), &configManager);
142 stickers::initialize(httpsClient.get());
143 stage_instances::initialize(httpsClient.get());
144 threads::initialize(httpsClient.get());
145 web_hooks::initialize(httpsClient.get());
146 users::initialize(httpsClient.get(), &configManager);
147 }
148
149 const config_manager& discord_core_client::getConfigManager() const {
150 return configManager;
151 }
152
154 return discord_core_client::currentUser;
155 }
156
158 try {
159 if (!instantiateWebSockets()) {
160 doWeQuit.store(true, std::memory_order_release);
161 return;
162 }
163 while (getBotUser().id == 0) {
164 std::this_thread::sleep_for(1ms);
165 }
166 registerFunctionsInternal();
167 while (!doWeQuit.load(std::memory_order_acquire)) {
168 std::this_thread::sleep_for(1ms);
169 }
170 } catch (const dca_exception& error) {
172 }
173 }
174
175 void discord_core_client::registerFunction(const jsonifier::vector<jsonifier::string>& functionNames, unique_ptr<base_function>&& baseFunction,
176 const create_application_command_data& commandDataNew, bool alwaysRegister) {
177 create_application_command_data commandData{ commandDataNew };
178 commandData.alwaysRegister = alwaysRegister;
179 commandController.registerFunction(functionNames, std::move(baseFunction));
180 commandsToRegister.emplace_back(commandData);
181 }
182
184 return commandController;
185 }
186
190
192 return std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch()) - startupTimeSinceEpoch;
193 }
194
195 void discord_core_client::registerFunctionsInternal() {
196 if (getBotUser().id != 0) {
197 jsonifier::vector<application_command_data> theCommands{
198 application_commands::getGlobalApplicationCommandsAsync({ .applicationId = getBotUser().id, .withLocalizations = false }).get()
199 };
200 while (commandsToRegister.size() > 0) {
201 create_application_command_data data = commandsToRegister.front();
202 commandsToRegister.pop_front();
203 data.applicationId = getBotUser().id;
204 if (data.alwaysRegister) {
205 if (data.guildId != 0) {
206 application_commands::createGuildApplicationCommandAsync(*static_cast<create_guild_application_command_data*>(&data)).get();
207 } else {
208 application_commands::createGlobalApplicationCommandAsync(*static_cast<create_global_application_command_data*>(&data)).get();
209 }
210 } else {
211 jsonifier::vector<application_command_data> guildCommands{};
212 if (data.guildId != 0) {
213 guildCommands =
214 application_commands::getGuildApplicationCommandsAsync({ .applicationId = getBotUser().id, .withLocalizations = false, .guildId = data.guildId }).get();
215 }
216 bool doesItExist{};
217 for (auto& value: theCommands) {
218 if (value == data) {
219 doesItExist = true;
220 break;
221 }
222 }
223 for (auto& value: guildCommands) {
224 if (value == data) {
225 doesItExist = true;
226 break;
227 }
228 }
229 try {
230 if (!doesItExist) {
231 if (data.guildId != 0) {
232 application_commands::createGuildApplicationCommandAsync(*static_cast<create_guild_application_command_data*>(&data)).get();
233
234 } else {
235 application_commands::createGlobalApplicationCommandAsync(*static_cast<create_global_application_command_data*>(&data)).get();
236 }
237 }
238 } catch (dca_exception& error) {
240 }
241 }
242 }
243 }
244 }
245
246 gateway_bot_data discord_core_client::getGateWayBot() {
247 discord_core_internal::https_workload_data workload{ discord_core_internal::https_workload_type::Get_Gateway_Bot };
248 workload.workloadClass = discord_core_internal::https_workload_class::Get;
249 workload.relativePath = "/gateway/bot";
250 workload.callStack = "discord_core_client::getGateWayBot()";
251 gateway_bot_data data{};
252 httpsClient->submitWorkloadAndGetResult(workload, data);
253 return data;
254 }
255
256 bool discord_core_client::instantiateWebSockets() {
257 gateway_bot_data gatewayData{};
258 try {
259 gatewayData = getGateWayBot();
260 } catch (const discord_core_internal::https_error& error) {
262 return false;
263 }
264
265 if (gatewayData.url == "") {
266 message_printer::printError<print_message_type::general>("Failed to collect the connection url! closing! did you remember to "
267 "properly set your bot token?");
268 std::this_thread::sleep_for(5s);
269 return false;
270 }
271 if (configManager.getStartingShard() + configManager.getShardCountForThisProcess() > configManager.getTotalShardCount()) {
272 message_printer::printError<print_message_type::general>("your sharding options are incorrect! please fix it!");
273 std::this_thread::sleep_for(5s);
274 return false;
275 }
276 uint64_t workerCount = configManager.getTotalShardCount() <= std::jthread::hardware_concurrency() ? configManager.getTotalShardCount()
277 : static_cast<uint64_t>(std::jthread::hardware_concurrency());
278
279 if (configManager.getConnectionAddress() == "") {
280 configManager.setConnectionAddress(gatewayData.url.substr(gatewayData.url.find("wss://") + jsonifier::string{ "wss://" }.size()));
281 }
282 if (configManager.getConnectionPort() == 0) {
283 configManager.setConnectionPort(443);
284 }
285 areWeReadyToConnect.store(false, std::memory_order_release);
286 baseSocketAgentsMap.reserve(workerCount);
287 for (uint64_t x = 0; x < configManager.getTotalShardCount(); ++x) {
288 if (baseSocketAgentsMap.size() < workerCount) {
289 baseSocketAgentsMap[x] = makeUnique<discord_core_internal::base_socket_agent>(&doWeQuit);
290 baseSocketAgentsMap[x]->shardMap.reserve(configManager.getTotalShardCount() / workerCount);
291 }
292 baseSocketAgentsMap[x % workerCount]->shardMap[x] = discord_core_internal::websocket_client{ x, &doWeQuit };
293 }
294 areWeReadyToConnect.store(true, std::memory_order_release);
295 while (!areWeFullyConnected()) {
296 std::this_thread::sleep_for(1ms);
297 }
298 for (auto& value: configManager.getFunctionsToExecute()) {
299 executeFunctionAfterTimePeriod(value.function, value.intervalInMs, value.repeated, false, this);
300 }
301 startupTimeSinceEpoch = std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch());
302 return true;
303 }
304
305 bool discord_core_client::areWeFullyConnected() {
306 for (auto& [key, value]: baseSocketAgentsMap) {
307 for (auto& [keyNew, valueNew]: value->shardMap) {
308 if (!valueNew.areWeConnected()) {
309 return false;
310 }
311 }
312 }
313 return true;
314 }
315
316 discord_core_client::~discord_core_client() {
317 instancePtr.release();
318 }
319
320 bot_user discord_core_client::currentUser{};
321}// namespace discord_core_api
static co_routine< jsonifier::vector< application_command_data > > getGlobalApplicationCommandsAsync(get_global_application_commands_data dataPackage)
Get all of the global application_commands for this bot.
static co_routine< jsonifier::vector< application_command_data > > getGuildApplicationCommandsAsync(get_guild_application_commands_data dataPackage)
Get all of the guild application_commands for a single guild for this bot.
static co_routine< application_command_data > createGlobalApplicationCommandAsync(create_global_application_command_data dataPackage)
Create a global application_command_data for this bot.
static co_routine< application_command_data > createGuildApplicationCommandAsync(create_guild_application_command_data dataPackage)
Create a guild application_command_data for a single server for this bot.
A type of user_data, to represent the bot and some of its associated endpoints.
A class for handling commands from user input.
discord_core_client - the main class for this library.
command_controller & getCommandController()
For collecting a reference to the command_controller.
event_manager & getEventManager()
For collecting a reference to the event_manager.
void runBot()
Executes the library, and waits for completion.
discord_core_client(const discord_core_client_config &configData)
discord_core_client constructor.
void registerFunction(const jsonifier::vector< jsonifier::string > &functionNames, unique_ptr< base_function > &&baseFunction, const create_application_command_data &commandData, bool alwaysRegister=false)
For registering a function with the command_controller.
static bot_user getBotUser()
For collecting a copy of the current bot's user_data.
milliseconds getTotalUpTime()
For collecting, the total time in milliseconds that this bot has been up for.
event_manager eventManager
An event-manager, for hooking into discord-api-events sent over the websockets.
const config_manager & getConfigManager() const
For collecting a reference to the config_manager.
Class for handling the assignment of event-handling functions.int32_t.
static DCA_INLINE void printError(const string_type &what, std::source_location where=std::source_location::current())
Print an error message of the specified type.
Definition Base.hpp:252
static DCA_INLINE void initialize(const value_type &other)
Initialize the message_printer with configuration settings and output/error streams.
Definition Base.hpp:236
A class representing a snowflake identifier with various operations.
Definition Base.hpp:701
A class representing the song apis.
Definition SongAPI.hpp:45
A smart pointer class that provides unique ownership semantics.
Definition UniquePtr.hpp:44
voice_connection class - represents the connection to a given voice channel_data.
DCA_INLINE unique_ptr< value_type, deleter > makeUnique(arg_types &&... args)
Helper function to create a unique_ptr for a non-array object.
The main namespace for the forward-facing interfaces.
Configuration data for the library's main class, discord_core_client.
Data from the get_gateway_bot endpoint.