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 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
3
4 Copyright 2021, 2022, 2023 Chris M. (RealTimeChris)
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19 USA
20*/
21/// DiscordCoreClient01.cpp - Source file for the main/exposed DiscordCoreClient class.
22/// May 12, 2021
23/// https://discordcoreapi.com
24/// \file DiscordCoreClient.cpp
25
27#include <csignal>
28#include <atomic>
29
30namespace DiscordCoreAPI {
31
32 namespace Globals {
33 SongAPIMap songAPIMap{};
34 VoiceConnectionsMap voiceConnectionMap{};
35 SoundCloudAPIMap soundCloudAPIMap{};
36 YouTubeAPIMap youtubeAPIMap{};
37 std::atomic_bool doWeQuit{};
38 }
39
40 DiscordCoreInternal::SoundCloudAPI* DiscordCoreClient::getSoundCloudAPI(Snowflake guildId) {
41 GuildData guild = Guilds::getCachedGuild({ .guildId = guildId });
42 if (!Globals::soundCloudAPIMap.contains(guildId.operator uint64_t())) {
43 Globals::soundCloudAPIMap[guildId.operator uint64_t()] =
44 std::make_unique<DiscordCoreInternal::SoundCloudAPI>(guild.discordCoreClient, guild.discordCoreClient->httpsClient.get(), guildId);
45 }
46 return Globals::soundCloudAPIMap[guildId.operator uint64_t()].get();
47 }
48
49 DiscordCoreInternal::YouTubeAPI* DiscordCoreClient::getYouTubeAPI(Snowflake guildId) {
50 GuildData guild = Guilds::getCachedGuild({ .guildId = guildId });
51 if (!Globals::youtubeAPIMap.contains(guildId.operator uint64_t())) {
52 Globals::youtubeAPIMap[guildId.operator uint64_t()] =
53 std::make_unique<DiscordCoreInternal::YouTubeAPI>(guild.discordCoreClient, guild.discordCoreClient->httpsClient.get(), guildId);
54 }
55 return Globals::youtubeAPIMap[guildId.operator uint64_t()].get();
56 }
57
58 VoiceConnection* DiscordCoreClient::getVoiceConnection(Snowflake guildId) {
59 GuildData guild = Guilds::getCachedGuild({ .guildId = guildId });
60 if (!Globals::voiceConnectionMap.contains(guildId.operator uint64_t())) {
61 uint64_t theShardId{ (guildId.operator uint64_t() >> 22) % guild.discordCoreClient->configManager.getTotalShardCount() };
62 uint64_t baseSocketIndex{ theShardId % guild.discordCoreClient->baseSocketAgentsMap.size() };
63 auto baseSocketAgent = guild.discordCoreClient->baseSocketAgentsMap[baseSocketIndex].get();
64 Globals::voiceConnectionMap[guildId.operator uint64_t()] = std::make_unique<VoiceConnection>(guild.discordCoreClient,
65 static_cast<DiscordCoreInternal::WebSocketClient*>(baseSocketAgent->shardMap[theShardId].get()), &Globals::doWeQuit);
66 }
67 guild.voiceConnectionPtr = Globals::voiceConnectionMap[guildId.operator uint64_t()].get();
68 return guild.voiceConnectionPtr;
69 }
70
71 SongAPI* DiscordCoreClient::getSongAPI(Snowflake guildId) {
72 if (!Globals::songAPIMap.contains(guildId.operator uint64_t())) {
73 Globals::songAPIMap[guildId.operator uint64_t()] = std::make_unique<SongAPI>(guildId);
74 }
75 return Globals::songAPIMap[guildId.operator uint64_t()].get();
76 }
77
78 void atexitHandler() {
79 Globals::doWeQuit.store(true);
80 }
81
82 SIGTERMError::SIGTERMError(const std::string& string) : DCAException(string){};
83
84 SIGSEGVError::SIGSEGVError(const std::string& string) : DCAException(string){};
85
86 SIGINTError::SIGINTError(const std::string& string) : DCAException(string){};
87
88 SIGILLError::SIGILLError(const std::string& string) : DCAException(string){};
89
90 SIGABRTError::SIGABRTError(const std::string& string) : DCAException(string){};
91
92 SIGFPEError::SIGFPEError(const std::string& string) : DCAException(string){};
93
94 void signalHandler(int32_t value) {
95 try {
96 switch (value) {
97 case SIGTERM: {
98 throw SIGTERMError{ "Exiting for: SIGTERM." };
99 }
100 case SIGSEGV: {
101 throw SIGSEGVError{ "Exiting for: SIGSEGV." };
102 }
103 case SIGINT: {
104 throw SIGINTError{ "Exiting for: SIGINT." };
105 }
106 case SIGILL: {
107 throw SIGILLError{ "Exiting for: SIGILL." };
108 }
109 case SIGABRT: {
110 throw SIGABRTError{ "Exiting for: SIGABRT." };
111 }
112 case SIGFPE: {
113 throw SIGFPEError{ "Exiting for: SIGFPE." };
114 }
115 }
116 } catch (SIGINTError&) {
117 reportException("signalHandler()");
118 Globals::doWeQuit.store(true);
119 } catch (...) {
120 reportException("signalHandler()");
121 std::exit(EXIT_FAILURE);
122 }
123 }
124
125 DiscordCoreClient::DiscordCoreClient(DiscordCoreClientConfig configData) {
126 std::atexit(&atexitHandler);
127 std::signal(SIGTERM, &signalHandler);
128 std::signal(SIGSEGV, &signalHandler);
129 std::signal(SIGINT, &signalHandler);
130 std::signal(SIGILL, &signalHandler);
131 std::signal(SIGABRT, &signalHandler);
132 std::signal(SIGFPE, &signalHandler);
133 configManager = ConfigManager{ configData };
134 if (!DiscordCoreInternal::SSLConnectionInterface::initialize()) {
135 if (configManager.doWePrintGeneralErrorMessages()) {
136 cout << shiftToBrightRed() << "Failed to initialize the SSL_CTX structure!" << reset() << endl << endl;
137 }
138 return;
139 }
140 if (configManager.doWePrintFFMPEGSuccessMessages()) {
141 av_log_set_level(AV_LOG_INFO);
142 } else if (configManager.doWePrintFFMPEGErrorMessages()) {
143 av_log_set_level(AV_LOG_ERROR);
144 } else if (!configManager.doWePrintFFMPEGErrorMessages() && !configManager.doWePrintFFMPEGSuccessMessages()) {
145 av_log_set_level(AV_LOG_QUIET);
146 }
147 if (sodium_init() == -1) {
148 if (configManager.doWePrintGeneralErrorMessages()) {
149 cout << shiftToBrightRed() << "LibSodium failed to initialize!" << reset() << endl << endl;
150 }
151 return;
152 }
153 httpsClient = std::make_unique<DiscordCoreInternal::HttpsClient>(&configManager);
154 ApplicationCommands::initialize(httpsClient.get());
155 AutoModerationRules::initialize(httpsClient.get());
156 Channels::initialize(httpsClient.get(), &configManager);
157 Guilds::initialize(httpsClient.get(), this, &configManager);
158 GuildMembers::initialize(httpsClient.get(), &configManager);
159 GuildScheduledEvents::initialize(httpsClient.get());
160 Interactions::initialize(httpsClient.get());
161 Messages::initialize(httpsClient.get());
162 Reactions::initialize(httpsClient.get());
163 Roles::initialize(httpsClient.get(), &configManager);
164 Stickers::initialize(httpsClient.get());
165 StageInstances::initialize(httpsClient.get());
166 Threads::initialize(httpsClient.get());
167 WebHooks::initialize(httpsClient.get());
168 Users::initialize(httpsClient.get(), &configManager);
169 didWeStartCorrectly = true;
170 }
171
172 ConfigManager& DiscordCoreClient::getConfigManager() {
173 return configManager;
174 }
175
176 BotUser DiscordCoreClient::getBotUser() {
177 return DiscordCoreClient::currentUser;
178 }
179
180 void DiscordCoreClient::runBot() {
181 if (!didWeStartCorrectly) {
182 return;
183 }
184 if (!instantiateWebSockets()) {
185 Globals::doWeQuit.store(true);
186 return;
187 }
188 while (!Globals::doWeQuit.load()) {
189 std::this_thread::sleep_for(1ms);
190 }
191 }
192
193
194 void DiscordCoreClient::registerFunction(const std::vector<std::string>& functionNames, std::unique_ptr<BaseFunction> baseFunction,
195 CreateApplicationCommandData commandData, bool alwaysRegister) {
196 commandData.alwaysRegister = alwaysRegister;
197 commandController.registerFunction(functionNames, std::move(baseFunction));
198 commandsToRegister.emplace_back(commandData);
199 }
200
201 CommandController& DiscordCoreClient::getCommandController() {
202 return commandController;
203 }
204
205 EventManager& DiscordCoreClient::getEventManager() {
206 return eventManager;
207 }
208
209 Milliseconds DiscordCoreClient::getTotalUpTime() {
210 return std::chrono::duration_cast<Milliseconds>(SysClock::now().time_since_epoch()) - startupTimeSinceEpoch;
211 }
212
213 void DiscordCoreClient::registerFunctionsInternal() {
214 std::vector<ApplicationCommand> theCommands{
215 ApplicationCommands::getGlobalApplicationCommandsAsync({ .applicationId = getBotUser().id, .withLocalizations = false }).get()
216 };
217 while (commandsToRegister.size() > 0) {
218 CreateApplicationCommandData data = commandsToRegister.front();
219 commandsToRegister.pop_front();
220 data.applicationId = getBotUser().id;
221 if (data.alwaysRegister) {
222 if (data.guildId != 0) {
223 ApplicationCommands::createGuildApplicationCommandAsync(*static_cast<CreateGuildApplicationCommandData*>(&data)).get();
224 } else {
225 ApplicationCommands::createGlobalApplicationCommandAsync(*static_cast<CreateGlobalApplicationCommandData*>(&data)).get();
226 }
227 } else {
228 std::vector<ApplicationCommand> guildCommands{};
229 if (data.guildId != 0) {
230 guildCommands = ApplicationCommands::getGuildApplicationCommandsAsync(
231 { .applicationId = getBotUser().id, .withLocalizations = false, .guildId = data.guildId })
232 .get();
233 }
234 bool doesItExist{};
235 for (auto& value: theCommands) {
236 if (*static_cast<ApplicationCommandData*>(&value) == *static_cast<ApplicationCommandData*>(&data)) {
237 doesItExist = true;
238 }
239 }
240 for (auto& value: guildCommands) {
241 if (*static_cast<ApplicationCommandData*>(&value) == *static_cast<ApplicationCommandData*>(&data)) {
242 doesItExist = true;
243 }
244 }
245 if (!doesItExist) {
246 if (data.guildId != 0) {
247 ApplicationCommands::createGuildApplicationCommandAsync(*static_cast<CreateGuildApplicationCommandData*>(&data)).get();
248
249 } else {
250 ApplicationCommands::createGlobalApplicationCommandAsync(*static_cast<CreateGlobalApplicationCommandData*>(&data)).get();
251 }
252 }
253 }
254 }
255 }
256
257 GatewayBotData DiscordCoreClient::getGateWayBot() {
258 try {
259 DiscordCoreInternal::HttpsWorkloadData workload{ DiscordCoreInternal::HttpsWorkloadType::Get_Gateway_Bot };
260 workload.workloadClass = DiscordCoreInternal::HttpsWorkloadClass::Get;
261 workload.relativePath = "/gateway/bot";
262 workload.callStack = "DiscordCoreClient::getGateWayBot()";
263 GatewayBotData data{};
264 httpsClient->submitWorkloadAndGetResult<GatewayBotData>(workload, data);
265 return data;
266 } catch (...) {
267 if (configManager.doWePrintGeneralErrorMessages()) {
268 reportException("DiscordCoreClient::getGateWayBot()");
269 }
270 }
271 return {};
272 }
273
274 bool DiscordCoreClient::instantiateWebSockets() {
275 GatewayBotData gatewayData{ getGateWayBot() };
276
277 if (gatewayData.url == "") {
278 if (configManager.doWePrintGeneralErrorMessages()) {
279 cout << shiftToBrightRed()
280 << "Failed to collect the connection URL! Closing! Did you remember to "
281 "properly set your bot token?"
282 << reset() << endl
283 << endl;
284 }
285 std::this_thread::sleep_for(5s);
286 return false;
287 }
288 if (configManager.getStartingShard() + configManager.getShardCountForThisProcess() > configManager.getTotalShardCount()) {
289 if (configManager.doWePrintGeneralErrorMessages()) {
290 cout << shiftToBrightRed() << "Your sharding options are incorrect! Please fix it!" << reset() << endl << endl;
291 }
292 std::this_thread::sleep_for(5s);
293 return false;
294 }
295 uint32_t theWorkerCount = configManager.getTotalShardCount() <= std::thread::hardware_concurrency() ? configManager.getTotalShardCount()
296 : std::thread::hardware_concurrency();
297 if (configManager.getConnectionAddress() == "") {
298 configManager.setConnectionAddress(gatewayData.url.substr(gatewayData.url.find("wss://") + std::string("wss://").size()));
299 }
300 if (configManager.getConnectionPort() == 0) {
301 configManager.setConnectionPort(443);
302 }
303 for (uint32_t x = 0; x < configManager.getTotalShardCount(); ++x) {
304 if (!baseSocketAgentsMap.contains(x % theWorkerCount)) {
305 while (!connectionStopWatch01.hasTimePassed()) {
306 std::this_thread::sleep_for(1ms);
307 }
308 connectionStopWatch01.resetTimer();
309 baseSocketAgentsMap[x % theWorkerCount] =
310 std::make_unique<DiscordCoreInternal::BaseSocketAgent>(this, &Globals::doWeQuit, x % theWorkerCount);
311 }
312 ConnectionPackage data{};
313 data.currentShard = x;
314 data.currentReconnectTries = 0;
315 baseSocketAgentsMap[x % theWorkerCount]->shardMap[x] =
316 std::make_unique<DiscordCoreInternal::WebSocketClient>(this, data.currentShard, &Globals::doWeQuit, false);
317 baseSocketAgentsMap[x % theWorkerCount]->getClient(x).connections = std::make_unique<ConnectionPackage>(data);
318 }
319
320 for (auto& value: configManager.getFunctionsToExecute()) {
321 if (value.repeated) {
322 TimeElapsedHandlerNoArgs onSend = [=, this]() -> void {
323 value.function(this);
324 };
325 ThreadPool::storeThread(onSend, value.intervalInMs);
326 } else {
327 ThreadPool::executeFunctionAfterTimePeriod(value.function, value.intervalInMs, false, this);
328 }
329 }
330 startupTimeSinceEpoch = std::chrono::duration_cast<Milliseconds>(SysClock::now().time_since_epoch());
331 return true;
332 }
333
334 DiscordCoreClient::~DiscordCoreClient() noexcept {
335 for (auto& [key, value]: NewThreadAwaiterBase::threadPool.workerThreads) {
336 if (value.thread.joinable()) {
337 value.thread.request_stop();
338 value.thread.detach();
339 }
340 }
341 }
342
343 BotUser DiscordCoreClient::currentUser{};
344}
DiscordCoreAPI_Dll void reportException(const std::string &currentFunctionName, std::source_location location=std::source_location::current(), std::exception_ptr ptr=nullptr)
Prints the current file, line, and column from which the function is being called - typically from wi...
Definition: Utilities.cpp:610
DiscordCoreClient * discordCoreClient
A pointer to the DiscordCoreClient.
The main namespace for this library.
A class for handling commands from user input.
Class for handling the assignment of event-handling functions.int32_t.
A type of User, to represent the Bot and some of its associated endpoints.
static GuildData getCachedGuild(GetGuildData dataPackage)
Collects a Guild from the library's cache.
Configuration data for the library's main class, DiscordCoreClient.
Definition: Utilities.hpp:558