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_apimap soundCloudAPIMap{};
40 you_tube_apimap youtubeAPIMap{};
41 song_apimap 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: {
87 message_printer::printError<print_message_type::general>("SIGTERM ERROR.");
88 exit(EXIT_FAILURE);
89 }
90 case SIGSEGV: {
91 message_printer::printError<print_message_type::general>("SIGSEGV ERROR.");
92 exit(EXIT_FAILURE);
93 }
94 case SIGINT: {
95 message_printer::printError<print_message_type::general>("SIGINT ERROR.");
96 exit(EXIT_SUCCESS);
97 }
98 case SIGILL: {
99 message_printer::printError<print_message_type::general>("SIGILL ERROR.");
100 exit(EXIT_FAILURE);
101 }
102 case SIGABRT: {
103 message_printer::printError<print_message_type::general>("SIGABRT ERROR.");
104 exit(EXIT_FAILURE);
105 }
106 case SIGFPE: {
107 message_printer::printError<print_message_type::general>("SIGFPE ERROR.");
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) {
171 message_printer::printError<print_message_type::general>(error.what());
172 }
173 }
174
175 void discord_core_client::registerFunction(const jsonifier::vector<jsonifier::string>& functionNames, unique_ptr<base_function> baseFunction,
176 create_application_command_data commandData, bool alwaysRegister) {
177 commandData.alwaysRegister = alwaysRegister;
178 commandController.registerFunction(functionNames, std::move(baseFunction));
179 commandsToRegister.emplace_back(commandData);
180 }
181
183 return commandController;
184 }
185
187 return eventManager;
188 }
189
191 return std::chrono::duration_cast<milliseconds>(hrclock::now().time_since_epoch()) - startupTimeSinceEpoch;
192 }
193
194 void discord_core_client::registerFunctionsInternal() {
195 if (getBotUser().id != 0) {
196 jsonifier::vector<application_command_data> theCommands{
197 application_commands::getGlobalApplicationCommandsAsync({ .applicationId = getBotUser().id, .withLocalizations = false }).get()
198 };
199 while (commandsToRegister.size() > 0) {
200 create_application_command_data data = commandsToRegister.front();
201 commandsToRegister.pop_front();
202 data.applicationId = getBotUser().id;
203 if (data.alwaysRegister) {
204 if (data.guildId != 0) {
205 application_commands::createGuildApplicationCommandAsync(*static_cast<create_guild_application_command_data*>(&data)).get();
206 } else {
207 application_commands::createGlobalApplicationCommandAsync(*static_cast<create_global_application_command_data*>(&data)).get();
208 }
209 } else {
210 jsonifier::vector<application_command_data> guildCommands{};
211 if (data.guildId != 0) {
212 guildCommands =
213 application_commands::getGuildApplicationCommandsAsync({ .applicationId = getBotUser().id, .withLocalizations = false, .guildId = data.guildId }).get();
214 }
215 bool doesItExist{};
216 for (auto& value: theCommands) {
217 if (value == data) {
218 doesItExist = true;
219 break;
220 }
221 }
222 for (auto& value: guildCommands) {
223 if (value == data) {
224 doesItExist = true;
225 break;
226 }
227 }
228 try {
229 if (!doesItExist) {
230 if (data.guildId != 0) {
231 application_commands::createGuildApplicationCommandAsync(*static_cast<create_guild_application_command_data*>(&data)).get();
232
233 } else {
234 application_commands::createGlobalApplicationCommandAsync(*static_cast<create_global_application_command_data*>(&data)).get();
235 }
236 }
237 } catch (dca_exception& error) {
238 message_printer::printError<print_message_type::https>(error.what());
239 }
240 }
241 }
242 }
243 }
244
245 gateway_bot_data discord_core_client::getGateWayBot() {
246 discord_core_internal::https_workload_data workload{ discord_core_internal::https_workload_type::Get_Gateway_Bot };
247 workload.workloadClass = discord_core_internal::https_workload_class::Get;
248 workload.relativePath = "/gateway/bot";
249 workload.callStack = "discord_core_client::getGateWayBot()";
250 gateway_bot_data data{};
251 httpsClient->submitWorkloadAndGetResult(workload, data);
252 return data;
253 }
254
255 bool discord_core_client::instantiateWebSockets() {
256 gateway_bot_data gatewayData{};
257 try {
258 gatewayData = getGateWayBot();
259 } catch (const discord_core_internal::https_error& error) {
260 message_printer::printError<print_message_type::general>(error.what());
261 return false;
262 }
263
264 if (gatewayData.url == "") {
265 message_printer::printError<print_message_type::general>("Failed to collect the connection url! closing! did you remember to "
266 "properly set your bot token?");
267 std::this_thread::sleep_for(5s);
268 return false;
269 }
270 if (configManager.getStartingShard() + configManager.getShardCountForThisProcess() > configManager.getTotalShardCount()) {
271 message_printer::printError<print_message_type::general>("your sharding options are incorrect! please fix it!");
272 std::this_thread::sleep_for(5s);
273 return false;
274 }
275 uint64_t workerCount = configManager.getTotalShardCount() <= std::jthread::hardware_concurrency() ? configManager.getTotalShardCount()
276 : static_cast<uint64_t>(std::jthread::hardware_concurrency());
277
278 if (configManager.getConnectionAddress() == "") {
279 configManager.setConnectionAddress(gatewayData.url.substr(gatewayData.url.find("wss://") + jsonifier::string{ "wss://" }.size()));
280 }
281 if (configManager.getConnectionPort() == 0) {
282 configManager.setConnectionPort(443);
283 }
284 areWeReadyToConnect.store(false, std::memory_order_release);
285 baseSocketAgentsMap.reserve(workerCount);
286 for (uint64_t x = 0; x < configManager.getTotalShardCount(); ++x) {
287 if (baseSocketAgentsMap.size() < workerCount) {
288 baseSocketAgentsMap[x] = makeUnique<discord_core_internal::base_socket_agent>(&doWeQuit);
289 baseSocketAgentsMap[x]->shardMap.reserve(configManager.getTotalShardCount() / workerCount);
290 }
291 baseSocketAgentsMap[x % workerCount]->shardMap[x] = discord_core_internal::websocket_client{ x, &doWeQuit };
292 }
293 areWeReadyToConnect.store(true, std::memory_order_release);
294 while (!areWeFullyConnected()) {
295 std::this_thread::sleep_for(1ms);
296 }
297 for (auto& value: configManager.getFunctionsToExecute()) {
298 executeFunctionAfterTimePeriod(value.function, value.intervalInMs, value.repeated, false, this);
299 }
300 startupTimeSinceEpoch = std::chrono::duration_cast<milliseconds>(hrclock::now().time_since_epoch());
301 return true;
302 }
303
304 bool discord_core_client::areWeFullyConnected() {
305 for (auto& [key, value]: baseSocketAgentsMap) {
306 for (auto& [keyNew, valueNew]: value->shardMap) {
307 if (!valueNew.areWeConnected()) {
308 return false;
309 }
310 }
311 }
312 return true;
313 }
314
315 discord_core_client::~discord_core_client() {
316 instancePtr.release();
317 }
318
319 bot_user discord_core_client::currentUser{};
320}// namespace discord_core_api
static co_routine< jsonifier::vector< application_command_data > > getGlobalApplicationCommandsAsync(const get_global_application_commands_data dataPackage)
Get all of the global application_commands 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.
static co_routine< jsonifier::vector< application_command_data > > getGuildApplicationCommandsAsync(const get_guild_application_commands_data dataPackage)
Get all of the guild application_commands for a single guild 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.
void registerFunction(const jsonifier::vector< jsonifier::string > &functionNames, unique_ptr< base_function > baseFunction)
Registers a function to be called.
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, 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 void initialize(const value_type &other)
Initialize the message_printer with configuration settings and output/error streams.
Definition: Base.hpp:206
A smart pointer class that provides unique ownership semantics.
Definition: UniquePtr.hpp:44
snowflake id
The user's id.
The main namespace for the forward-facing interfaces.
An exception class derived from std::runtime_error for dca-related exceptions.
Definition: Base.hpp:820
Configuration data for the library's main class, discord_core_client.
Definition: Utilities.hpp:237