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 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 VoiceConnectionMap voiceConnectionMap{};
35 SoundCloudAPIMap soundCloudAPIMap{};
36 YouTubeAPIMap youtubeAPIMap{};
37 AtomicBool doWeQuit{ false };
38 }
39
40 DiscordCoreInternal::SoundCloudAPI* DiscordCoreClient::getSoundCloudAPI(Snowflake guildId) {
41 GuildData theGuildNew{};
42 theGuildNew.id = guildId;
43 const GuildData* theGuild = &Guilds::cache.at(theGuildNew);
44 if (!Globals::soundCloudAPIMap.contains(guildId)) {
45 Globals::soundCloudAPIMap[guildId] =
46 std::make_unique<DiscordCoreInternal::SoundCloudAPI>(&theGuild->discordCoreClient->configManager, theGuild->discordCoreClient->httpsClient.get(), guildId);
47 }
48 return Globals::soundCloudAPIMap[guildId].get();
49 }
50
51 DiscordCoreInternal::YouTubeAPI* DiscordCoreClient::getYouTubeAPI(Snowflake guildId) {
52 GuildData theGuildNew{};
53 theGuildNew.id = guildId;
54 const GuildData* theGuild = &Guilds::cache.at(theGuildNew);
55 if (!Globals::youtubeAPIMap.contains(guildId)) {
56 Globals::youtubeAPIMap[guildId] =
57 std::make_unique<DiscordCoreInternal::YouTubeAPI>(&theGuild->discordCoreClient->configManager, theGuild->discordCoreClient->httpsClient.get(), guildId);
58 }
59 return Globals::youtubeAPIMap[guildId].get();
60 }
61
62 VoiceConnection* DiscordCoreClient::getVoiceConnection(Snowflake guildId) {
63 GuildData theGuildNew{};
64 theGuildNew.id = guildId;
65 GuildData* theGuild = &Guilds::cache[theGuildNew];
66 if (!Globals::voiceConnectionMap.contains(guildId)) {
67 Uint64 theShardId{ (guildId >> 22) % theGuild->discordCoreClient->configManager.getTotalShardCount() };
68 Uint64 baseSocketIndex{ theShardId % theGuild->discordCoreClient->baseSocketAgentMap.size() };
69 auto baseSocketAgent = theGuild->discordCoreClient->baseSocketAgentMap[baseSocketIndex].get();
70 Globals::voiceConnectionMap[guildId] = std::make_unique<VoiceConnection>(baseSocketAgent, baseSocketAgent->theShardMap[theShardId].get(),
71 DiscordCoreInternal::VoiceConnectInitData{}, &theGuild->discordCoreClient->configManager, &Globals::doWeQuit, StreamType::None);
72 }
73 theGuild->voiceConnectionPtr = Globals::voiceConnectionMap[guildId].get();
74 return theGuild->voiceConnectionPtr;
75 }
76
77 SongAPI* DiscordCoreClient::getSongAPI(Snowflake guildId) {
78 if (!Globals::songAPIMap.contains(guildId)) {
79 Globals::songAPIMap[guildId] = std::make_unique<SongAPI>(guildId);
80 }
81 return Globals::songAPIMap[guildId].get();
82 }
83
84 void atexitHandler() {
85 Globals::doWeQuit.store(true);
86 }
87
88 SIGTERMError::SIGTERMError(String theString) : std::runtime_error(theString){};
89
90 SIGSEGVError::SIGSEGVError(String theString) : std::runtime_error(theString){};
91
92 SIGINTError::SIGINTError(String theString) : std::runtime_error(theString){};
93
94 SIGILLError::SIGILLError(String theString) : std::runtime_error(theString){};
95
96 SIGABRTError::SIGABRTError(String theString) : std::runtime_error(theString){};
97
98 SIGFPEError::SIGFPEError(String theString) : std::runtime_error(theString){};
99
100 void signalHandler(Int32 theValue) {
101 try {
102 switch (theValue) {
103 case SIGTERM: {
104 throw SIGTERMError{ "Exiting for: SIGTERM.\n" };
105 }
106 case SIGSEGV: {
107 throw SIGSEGVError{ "Exiting for: SIGSEGV.\n" };
108 }
109 case SIGINT: {
110 throw SIGINTError{ "Exiting for: SIGINT.\n" };
111 }
112 case SIGILL: {
113 throw SIGILLError{ "Exiting for: SIGILL.\n" };
114 }
115 case SIGABRT: {
116 throw SIGABRTError{ "Exiting for: SIGABRT.\n" };
117 }
118 case SIGFPE: {
119 throw SIGFPEError{ "Exiting for: SIGFPE.\n" };
120 }
121 }
122 } catch (SIGINTError&) {
123 reportException("signalHandler()");
124 std::exit(EXIT_SUCCESS);
125 } catch (...) {
126 reportException("signalHandler()");
127 }
128 std::exit(EXIT_FAILURE);
129 }
130
131 DiscordCoreClient::DiscordCoreClient(DiscordCoreClientConfig configData) {
132 std::atexit(&atexitHandler);
133 std::signal(SIGTERM, &signalHandler);
134 std::signal(SIGSEGV, &signalHandler);
135 std::signal(SIGINT, &signalHandler);
136 std::signal(SIGILL, &signalHandler);
137 std::signal(SIGABRT, &signalHandler);
138 std::signal(SIGFPE, &signalHandler);
139 if (!DiscordCoreInternal::SSLConnectionInterface::initialize()) {
140 if (this->configManager.doWePrintGeneralErrorMessages()) {
141 cout << shiftToBrightRed() << "Failed to initialize the SSL_CTX structure!" << reset() << endl << endl;
142 }
143 return;
144 }
145 this->configManager = ConfigManager{ configData };
146 if (this->configManager.doWePrintFFMPEGSuccessMessages()) {
147 av_log_set_level(AV_LOG_INFO);
148 }
149 if (this->configManager.doWePrintFFMPEGErrorMessages()) {
150 av_log_set_level(AV_LOG_ERROR);
151 }
152 if (!this->configManager.doWePrintFFMPEGErrorMessages() && !this->configManager.doWePrintFFMPEGSuccessMessages()) {
153 av_log_set_level(AV_LOG_QUIET);
154 }
155 if (sodium_init() == -1) {
156 if (this->configManager.doWePrintGeneralErrorMessages()) {
157 cout << shiftToBrightRed() << "LibSodium failed to initialize!" << reset() << endl << endl;
158 }
159 return;
160 }
161 this->httpsClient = std::make_unique<DiscordCoreInternal::HttpsClient>(&this->configManager);
162 ApplicationCommands::initialize(this->httpsClient.get());
163 AutoModerationRules::initialize(this->httpsClient.get());
164 Channels::initialize(this->httpsClient.get(), &this->configManager);
165 Guilds::initialize(this->httpsClient.get(), this, &this->configManager);
166 GuildMembers::initialize(this->httpsClient.get(), &this->configManager);
167 GuildScheduledEvents::initialize(this->httpsClient.get());
168 Interactions::initialize(this->httpsClient.get());
169 Messages::initialize(this->httpsClient.get());
170 Reactions::initialize(this->httpsClient.get());
171 Roles::initialize(this->httpsClient.get(), &this->configManager);
172 Stickers::initialize(this->httpsClient.get());
173 StageInstances::initialize(this->httpsClient.get());
174 Threads::initialize(this->httpsClient.get());
175 Users::initialize(this->httpsClient.get(), &this->configManager);
176 WebHooks::initialize(this->httpsClient.get());
177 this->didWeStartCorrectly = true;
178 }
179
180 void DiscordCoreClient::registerFunction(const std::vector<String>& functionNames, std::unique_ptr<BaseFunction> baseFunction, CreateApplicationCommandData commandData,
181 Bool alwaysRegister) {
182 commandData.alwaysRegister = alwaysRegister;
183 this->commandController.registerFunction(functionNames, std::move(baseFunction));
184 this->commandsToRegister.emplace_back(commandData);
185 }
186
187 void DiscordCoreClient::registerFunctionsInternal() {
188 std::vector<ApplicationCommand> theCommands{};
189 try {
190 theCommands = ApplicationCommands::getGlobalApplicationCommandsAsync({ .withLocalizations = false, .applicationId = this->getBotUser().id }).get();
191 } catch (...) {
192 reportException("DiscordCoreClient::registerFunctionsInternal()");
193 return;
194 }
195 while (this->commandsToRegister.size() > 0) {
196 CreateApplicationCommandData theData = this->commandsToRegister.front();
197 this->commandsToRegister.pop_front();
198 theData.applicationId = this->getBotUser().id;
199 if (theData.alwaysRegister) {
200 if (theData.guildId != 0) {
201 ApplicationCommands::createGuildApplicationCommandAsync(*static_cast<CreateGuildApplicationCommandData*>(&theData)).get();
202 } else {
203 ApplicationCommands::createGlobalApplicationCommandAsync(*static_cast<CreateGlobalApplicationCommandData*>(&theData)).get();
204 }
205 } else {
206 std::vector<ApplicationCommand> theGuildCommands{};
207 if (theData.guildId != 0) {
208 theGuildCommands =
209 ApplicationCommands::getGuildApplicationCommandsAsync({ .withLocalizations = false, .applicationId = this->getBotUser().id, .guildId = theData.guildId })
210 .get();
211 }
212 Bool doesItExist{ false };
213 for (auto& value: theCommands) {
214 if (value.name == theData.name) {
215 doesItExist = true;
216 }
217 }
218 for (auto& value: theGuildCommands) {
219 if (value.name == theData.name) {
220 doesItExist = true;
221 }
222 }
223 if (!doesItExist) {
224 if (theData.guildId != 0) {
225 ApplicationCommands::createGuildApplicationCommandAsync(*static_cast<CreateGuildApplicationCommandData*>(&theData)).get();
226 } else {
227 ApplicationCommands::createGlobalApplicationCommandAsync(*static_cast<CreateGlobalApplicationCommandData*>(&theData)).get();
228 }
229 }
230 }
231 }
232 }
233
234 CommandController& DiscordCoreClient::getCommandController() {
235 return this->commandController;
236 }
237
238 EventManager& DiscordCoreClient::getEventManager() {
239 return this->eventManager;
240 }
241
242 BotUser DiscordCoreClient::getBotUser() {
243 return this->currentUser;
244 }
245
246 void DiscordCoreClient::runBot() {
247 if (!this->didWeStartCorrectly) {
248 return;
249 }
250 if (!this->instantiateWebSockets()) {
251 Globals::doWeQuit.store(true);
252 return;
253 }
254 while (!Globals::doWeQuit.load()) {
255 if (this->theConnections.size() > 0 && this->theConnectionStopWatch.hasTimePassed()) {
256 this->theConnectionStopWatch.resetTimer();
257 auto theData = this->theConnections.front();
258 this->theConnections.pop_front();
259 this->baseSocketAgentMap[theData.currentShard % this->baseSocketAgentMap.size()]->connect(theData);
260 if (this->theConnections.size() == 0) {
261 if (this->configManager.doWePrintGeneralSuccessMessages()) {
262 cout << shiftToBrightGreen() << "All of the shards are connected for the current process!" << reset() << endl << endl;
263 }
264 this->registerFunctionsInternal();
265 }
266 }
267 std::this_thread::sleep_for(1ms);
268 }
269 }
270
271 GatewayBotData DiscordCoreClient::getGateWayBot() {
272 try {
273 DiscordCoreInternal::HttpsWorkloadData workload{ DiscordCoreInternal::HttpsWorkloadType::Get_Gateway_Bot };
274 workload.workloadClass = DiscordCoreInternal::HttpsWorkloadClass::Get;
275 workload.relativePath = "/gateway/bot";
276 workload.callStack = "DiscordCoreClient::getGateWayBot()";
277 GatewayBotData theData{};
278 theData = this->httpsClient->submitWorkloadAndGetResult<GatewayBotData>(workload);
279 return theData;
280 } catch (...) {
281 reportException("DiscordCoreClient::getGatewayBot()");
282 return {};
283 }
284 }
285
286 Bool DiscordCoreClient::instantiateWebSockets() {
287 GatewayBotData gatewayData{ this->getGateWayBot() };
288
289 if (gatewayData.url == "") {
290 if (this->configManager.doWePrintGeneralErrorMessages()) {
291 cout << shiftToBrightRed()
292 << "Failed to collect the connection URL! Closing! Did you remember to "
293 "properly set your bot token?"
294 << reset() << endl
295 << endl;
296 }
297 std::this_thread::sleep_for(5s);
298 return false;
299 }
300 if (this->configManager.getStartingShard() + this->configManager.getShardCountForThisProcess() > this->configManager.getTotalShardCount()) {
301 if (this->configManager.doWePrintGeneralErrorMessages()) {
302 cout << shiftToBrightRed() << "Your sharding options are incorrect! Please fix it!" << reset() << endl << endl;
303 }
304 std::this_thread::sleep_for(5s);
305 return false;
306 }
307 Uint32 theWorkerCount =
308 this->configManager.getTotalShardCount() <= std::thread::hardware_concurrency() ? this->configManager.getTotalShardCount() : std::thread::hardware_concurrency();
309 if (this->configManager.getConnectionAddress() == "") {
310 this->configManager.setConnectionAddress(gatewayData.url.substr(gatewayData.url.find("wss://") + String("wss://").size()));
311 }
312 if (this->configManager.getConnectionPort() == "") {
313 this->configManager.setConnectionPort("443");
314 }
315 for (Uint32 x = 0; x < this->configManager.getTotalShardCount(); ++x) {
316 if (!this->baseSocketAgentMap.contains(x % theWorkerCount)) {
317 this->baseSocketAgentMap[x % theWorkerCount] = std::make_unique<DiscordCoreInternal::BaseSocketAgent>(this, &Globals::doWeQuit, x % theWorkerCount);
318 }
319 ConnectionPackage theData{};
320 theData.currentShard = x;
321 theData.currentReconnectTries = 0;
322 this->baseSocketAgentMap[x % theWorkerCount]->theShardMap[x] =
323 std::make_unique<DiscordCoreInternal::WebSocketSSLShard>(this, &this->theConnections, x, &Globals::doWeQuit);
324 this->theConnections.emplace_back(theData);
325 }
326 try {
327 this->currentUser = BotUser{ Users::getCurrentUserAsync().get(), this->baseSocketAgentMap[this->configManager.getStartingShard()].get() };
328 } catch (...) {
329 reportException("DiscordCoreClient::instantiateWebSockets()");
330 }
331
332 for (auto& value: this->configManager.getFunctionsToExecute()) {
333 if (value.repeated) {
334 TimeElapsedHandlerNoArgs onSend = [=, this]() -> void {
335 value.function(this);
336 };
337 ThreadPool::storeThread(onSend, value.intervalInMs);
338 } else {
339 TimeElapsedHandler<void*> onSend = [=, this](void*) -> void {
340 value.function(this);
341 };
342 ThreadPool::executeFunctionAfterTimePeriod(onSend, value.intervalInMs, false, static_cast<void*>(&onSend));
343 }
344 }
345 return true;
346 }
347
348 DiscordCoreClient::~DiscordCoreClient() noexcept {
349 for (auto& [key, value]: CoRoutineBase::threadPool.workerThreads) {
350 if (value.theThread.joinable()) {
351 value.theThread.request_stop();
352 value.theThread.join();
353 }
354 }
355 }
356}
DiscordCoreAPI_Dll void reportException(const String &currentFunctionName, std::source_location theLocation=std::source_location::current())
Prints the current file, line, and column from which the function is being called - typically from wi...
Definition: Utilities.cpp:1371
The main namespace for this library.
A class for handling commands from user input.
Class for handling the assignment of event-handling functions.
Data from the GetGatewayBot endpoint.
A type of User, to represent the Bot and some of its associated endpoints.
Configuration data for the library's main class, DiscordCoreClient.
Definition: Utilities.hpp:635