DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
YouTubeAPI.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/// YouTubeAPI.cpp - soure file for the you_tube api related stuff.
27/// Jun 30, 2021
28/// https://discordcoreapi.com
29/// \file YouTubeAPI.cpp
30
38#include <regex>
39
40namespace jsonifier {
41
42 template<> struct core<discord_core_api::discord_core_internal::user> {
43 using value_type = discord_core_api::discord_core_internal::user;
44 static constexpr auto parseValue = createValue("lockedSafetyMode", &value_type::lockedSafetyMode);
45 };
46
47 template<> struct core<discord_core_api::discord_core_internal::request> {
48 using value_type = discord_core_api::discord_core_internal::request;
49 static constexpr auto parseValue = createValue("useSsl", &value_type::useSsl);
50 };
51
52 template<> struct core<discord_core_api::discord_core_internal::you_tube_request_client> {
53 using value_type = discord_core_api::discord_core_internal::you_tube_request_client;
54 static constexpr auto parseValue = createValue<&value_type::clientName, &value_type::androidSdkVersion, &value_type::clientVersion, &value_type::hl, &value_type::gl,
55 &value_type::osName, &value_type::osVersion, &value_type::platform>();
56 };
57
58 template<> struct core<discord_core_api::discord_core_internal::you_tube_request_context> {
59 using value_type = discord_core_api::discord_core_internal::you_tube_request_context;
60 static constexpr auto parseValue =
61 createValue("client", &value_type::client, "captionParams", &value_type::captionParams, "request", &value_type::requestVal, "user", &value_type::userVal);
62 };
63
64 template<> struct core<discord_core_api::discord_core_internal::you_tube_request> {
65 using value_type = discord_core_api::discord_core_internal::you_tube_request;
66 static constexpr auto parseValue = createValue("videoId", &value_type::videoId, "contentCheckOk", &value_type::contentCheckOk, "racyCheckOk", &value_type::racyCheckOk,
67 "context", &value_type::context, "playlistId", &value_type::playlistId, "params", &value_type::params);
68 };
69
70 template<> struct core<discord_core_api::discord_core_internal::thumbnail_element> {
71 using value_type = discord_core_api::discord_core_internal::thumbnail_element;
72 static constexpr auto parseValue = createValue("url", &value_type::url, "width", &value_type::width);
73 };
74
75 template<> struct core<discord_core_api::discord_core_internal::video_details_thumbnail> {
76 using value_type = discord_core_api::discord_core_internal::video_details_thumbnail;
77 static constexpr auto parseValue = createValue("thumbnails", &value_type::thumbnails);
78 };
79
80 template<> struct core<discord_core_api::discord_core_internal::video_details> {
81 using value_type = discord_core_api::discord_core_internal::video_details;
82 static constexpr auto parseValue = createValue("title", &value_type::title, "videoId", &value_type::videoId, "thumbnail", &value_type::thumbnail, "shortDescription",
83 &value_type::shortDescription, "lengthSeconds", &value_type::lengthSeconds);
84 };
85
86 template<> struct core<discord_core_api::discord_core_internal::format> {
87 using value_type = discord_core_api::discord_core_internal::format;
88 static constexpr auto parseValue =
89 createValue("url", &value_type::url, "mimeType", &value_type::mimeType, "bitrate", &value_type::bitrate, "contentLength", &value_type::contentLength);
90 };
91
92 template<> struct core<discord_core_api::discord_core_internal::streaming_data> {
93 using value_type = discord_core_api::discord_core_internal::streaming_data;
94 static constexpr auto parseValue = createValue("adaptiveFormats", &value_type::adaptiveFormats);
95 };
96
97 template<> struct core<discord_core_api::discord_core_internal::data> {
98 using value_type = discord_core_api::discord_core_internal::data;
99 static constexpr auto parseValue = createValue("streamingData", &value_type::streamingData, "videoDetails", &value_type::videoDetails);
100 };
101
102 template<> struct core<discord_core_api::discord_core_internal::video_renderer> {
103 using value_type = discord_core_api::discord_core_internal::video_renderer;
104 static constexpr auto parseValue = createValue("videoId", &value_type::videoId);
105 };
106
107 template<> struct core<discord_core_api::discord_core_internal::video_renderer_type> {
108 using value_type = discord_core_api::discord_core_internal::video_renderer_type;
109 static constexpr auto parseValue = createValue("videoRenderer", &value_type::videoRenderer);
110 };
111
112 template<> struct core<discord_core_api::discord_core_internal::item_section_renderer_contents> {
113 using value_type = discord_core_api::discord_core_internal::item_section_renderer_contents;
114 static constexpr auto parseValue = createValue("contents", &value_type::contents);
115 };
116
117 template<> struct core<discord_core_api::discord_core_internal::item_section_renderer> {
118 using value_type = discord_core_api::discord_core_internal::item_section_renderer;
119 static constexpr auto parseValue = createValue("itemSectionRenderer", &value_type::itemSectionRendererContents);
120 };
121
122 template<> struct core<discord_core_api::discord_core_internal::section_list_renderer> {
123 using value_type = discord_core_api::discord_core_internal::section_list_renderer;
124 static constexpr auto parseValue = createValue("contents", &value_type::contents);
125 };
126
127 template<> struct core<discord_core_api::discord_core_internal::primary_contents> {
128 using value_type = discord_core_api::discord_core_internal::primary_contents;
129 static constexpr auto parseValue = createValue("sectionListRenderer", &value_type::sectionListRenderer);
130 };
131
132 template<> struct core<discord_core_api::discord_core_internal::two_column_search_results_renderer> {
133 using value_type = discord_core_api::discord_core_internal::two_column_search_results_renderer;
134 static constexpr auto parseValue = createValue("primaryContents", &value_type::primaryContents);
135 };
136
137 template<> struct core<discord_core_api::discord_core_internal::contents01> {
138 using value_type = discord_core_api::discord_core_internal::contents01;
139 static constexpr auto parseValue = createValue("twoColumnSearchResultsRenderer", &value_type::twoColumnSearchResultsRenderer);
140 };
141
142 template<> struct core<discord_core_api::discord_core_internal::you_tube_search_results> {
143 using value_type = discord_core_api::discord_core_internal::you_tube_search_results;
144 static constexpr auto parseValue = createValue("contents", &value_type::contents);
145 };
146}
147
148namespace discord_core_api {
149
150 namespace discord_core_internal {
151
152 you_tube_request_builder::you_tube_request_builder(config_manager* configManagerNew) : https_client_core{ jsonifier::string{ configManagerNew->getBotToken() } } {};
153
154 inline bool collectVideoIdFromSearchQuery(jsonifier::string_view url, jsonifier::string& stringNew) {
155 std::regex pattern("v=([a-zA-Z0-9_\\-]+)");
156 std::smatch match;
157
158 auto newUrl = static_cast<std::string>(url);
159 if (std::regex_search(newUrl, match, pattern)) {
160 stringNew = match[1].str();
161 return true;
162 }
163
164 return false;
165 }
166
167 song you_tube_request_builder::collectSingleResult(jsonifier::string_view searchQuery) {
168
169 song songNew{};
170 songNew.type = song_type::YouTube;
171 songNew.songId = searchQuery;
172 songNew = constructDownloadInfo(songNew, 0);
173 return songNew;
174 }
175
176 jsonifier::vector<song> you_tube_request_builder::collectSearchResults(jsonifier::string_view searchQuery, uint64_t limit) {
177 https_workload_data dataPackage{ https_workload_type::YouTube_Get_Search_Results };
178 jsonifier::vector<song> searchResults{};
179 jsonifier::string theId{};
180 if (collectVideoIdFromSearchQuery(searchQuery, theId)) {
181 searchResults.emplace_back(collectSingleResult(theId));
182 return searchResults;
183 }
184 dataPackage.baseUrl = baseUrl;
185 dataPackage.headersToInsert["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36";
186 dataPackage.relativePath = "/results?search_query=" + urlEncode(searchQuery);
187 dataPackage.workloadClass = https_workload_class::Get;
188 https_response_data returnData = submitWorkloadAndGetResult(std::move(dataPackage));
189 if (returnData.responseCode != 200) {
190 message_printer::printError<print_message_type::https>(
191 "you_tube_request_builder::collectSearchResults() error: " + jsonifier::toString(returnData.responseCode.operator uint64_t()) + returnData.responseData);
192 }
193 auto varInitFind = returnData.responseData.find("var ytInitialData = ");
194 if (varInitFind != jsonifier::string::npos) {
195 jsonifier::string newString00 = "var ytInitialData = ";
196 jsonifier::string newString = returnData.responseData.substr(varInitFind + newString00.size());
197 jsonifier::string stringSequence = ";</script><script nonce=";
198 newString = newString.substr(0, newString.find(stringSequence));
199 you_tube_search_results you_tubeSearchResults{};
200 parser.parseJson(you_tubeSearchResults, newString);
201 for (auto& value: parser.getErrors()) {
202 message_printer::printError<print_message_type::https>(value.reportError());
203 }
204 for (auto& value: you_tubeSearchResults.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents) {
205 for (auto& value02: value.itemSectionRendererContents.contents) {
206 if (value02.videoRenderer.videoId != "") {
207 song songNew{};
208 songNew.type = song_type::YouTube;
209 songNew.songId = value02.videoRenderer.videoId;
210 searchResults.emplace_back(constructDownloadInfo(songNew, 0));
211 }
212 if (searchResults.size() >= limit) {
213 break;
214 }
215 }
216 }
217 }
218 return searchResults;
219 }
220
221 song you_tube_request_builder::constructDownloadInfo(const song& songNew, uint64_t currentRecursionDepth) {
222 https_response_data responseData{};
223 try {
224 discord_core_api::discord_core_internal::you_tube_request requestData{};
225 requestData.videoId = songNew.songId;
226 https_workload_data dataPackage02{ https_workload_type::YouTube_Get_Search_Results };
227 dataPackage02.baseUrl = "https://music.you_tube.com";
228 dataPackage02.headersToInsert["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
229 dataPackage02.headersToInsert["Origin"] = "https://music.you_tube.com";
230 dataPackage02.headersToInsert["Content-Type"] = "application/json; charset=utf-8";
231 dataPackage02.relativePath = "/you_tubei/v1/player?key=AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w";
232 parser.serializeJson(requestData, dataPackage02.content);
233 dataPackage02.content =
234 "{\"context\":{\"client\":{\"clientName\":\"IOS\",\"clientVersion\":\"17.13.3\",\"hl\":\"en\"},\"user\":{\"lockedSafetyMode\":false},\"request\":{\"useSsl\":"
235 "true},\"captionParams\":{}},\"params\":{}}";
236 std::cout << "REQUEST CONTENT: " << dataPackage02.content << std::endl;
237 dataPackage02.workloadClass = https_workload_class::Post;
238 responseData = submitWorkloadAndGetResult(std::move(dataPackage02));
239 if (responseData.responseCode != 204 && responseData.responseCode != 201 && responseData.responseCode != 200) {
240 message_printer::printError<print_message_type::https>("you_tube_request_builder::constructDownloadInfo() 01 error: " +
241 jsonifier::toString(responseData.responseCode.operator uint64_t()) + ", " + responseData.responseData);
242 }
243 data dataNew{};
244 jsonifier::vector<format> potentialFormats{};
245 parser.parseJson(dataNew, responseData.responseData);
246 for (auto& value: parser.getErrors()) {
247 message_printer::printError<print_message_type::https>(value.reportError());
248 }
249 for (auto& value: dataNew.streamingData.adaptiveFormats) {
250 if (value.mimeType == "audio/webm; codecs=\"opus\"") {
251 potentialFormats.emplace_back(value);
252 }
253 }
254 uint64_t currentMax{};
255 int64_t maxIndex{ static_cast<int64_t>(std::numeric_limits<uint64_t>::max()) };
256 for (uint64_t x = 0; x < potentialFormats.size(); ++x) {
257 if (potentialFormats.at(x).bitrate > static_cast<int64_t>(currentMax)) {
258 maxIndex = static_cast<int64_t>(x);
259 }
260 }
261
262 song newerSong{ songNew };
263 jsonifier::string downloadBaseUrl{};
264 newerSong.type = song_type::YouTube;
265 if (maxIndex > -1) {
266 jsonifier::string thumbnailUrl{};
267 if (dataNew.videoDetails.thumbnail.thumbnails.size() > 0) {
268 uint32_t currentLargestThumbnailWidth{};
269 uint32_t currentThumbnailIndex{};
270 for (uint32_t x = 0; x < dataNew.videoDetails.thumbnail.thumbnails.size(); ++x) {
271 if (dataNew.videoDetails.thumbnail.thumbnails[x].width > currentLargestThumbnailWidth) {
272 currentThumbnailIndex = x;
273 }
274 }
275 thumbnailUrl = dataNew.videoDetails.thumbnail.thumbnails[currentThumbnailIndex].url;
276 }
277 auto httpsFind = potentialFormats[static_cast<uint64_t>(maxIndex)].url.find("https://");
278 auto videoPlaybackFind = potentialFormats[static_cast<uint64_t>(maxIndex)].url.find("/videoplayback?");
279 if (httpsFind != jsonifier::string::npos && videoPlaybackFind != jsonifier::string::npos) {
280 jsonifier::string newString00 = "https://";
281 downloadBaseUrl = potentialFormats[static_cast<uint64_t>(maxIndex)].url.substr(httpsFind + newString00.size(), videoPlaybackFind - newString00.size());
282 }
283 auto& newString = dataNew.videoDetails.shortDescription;
284 if (newString.size() > 0) {
285 if (newString.size() > 256) {
286 newString = newString.substr(0, 256);
287 }
288 newerSong.description = utf8MakeValid(newString);
289 newerSong.description += "...";
290 }
291 jsonifier::string requestNew = potentialFormats[static_cast<uint64_t>(maxIndex)].url.substr(potentialFormats[static_cast<uint64_t>(maxIndex)].url.find(".com") + 4);
292 newerSong.finalDownloadUrls.resize(3);
293 download_url downloadUrl01{};
294 downloadUrl01.contentSize = jsonifier::strToUint64(potentialFormats[static_cast<uint64_t>(maxIndex)].contentLength.data());
295 downloadUrl01.urlPath = downloadBaseUrl;
296 download_url downloadUrl02{};
297 downloadUrl02.contentSize = jsonifier::strToUint64(potentialFormats[static_cast<uint64_t>(maxIndex)].contentLength.data());
298 downloadUrl02.urlPath = requestNew;
299 newerSong.finalDownloadUrls.at(0) = downloadUrl01;
300 newerSong.finalDownloadUrls.at(1) = downloadUrl02;
301 newerSong.viewUrl = newerSong.firstDownloadUrl;
302 newerSong.duration = time_stamp::convertMsToDurationString(jsonifier::strToUint64(dataNew.videoDetails.lengthSeconds) * 1000);
303 newerSong.viewUrl = "https://www.you_tube.com/watch?v=" + songNew.songId;
304 newerSong.contentLength = downloadUrl02.contentSize;
305 newerSong.thumbnailUrl = thumbnailUrl;
306 newerSong.songTitle = dataNew.videoDetails.title;
307 newerSong.type = song_type::YouTube;
308 }
309 return newerSong;
310 } catch (const https_error& error) {
311 if (currentRecursionDepth <= 10) {
312 ++currentRecursionDepth;
313 return constructDownloadInfo(songNew, currentRecursionDepth);
314 } else {
315 message_printer::printError<print_message_type::https>("you_tube_request_builder::constructDownloadInfo() error: " + jsonifier::string{ error.what() });
316 return {};
317 }
318 }
319 }
320
321 song you_tube_request_builder::collectFinalSong(const song& songNew) {
322 song newerSong{ songNew };
323 newerSong.firstDownloadUrl = static_cast<jsonifier::string>(baseUrl) + "/watch?v=" + newerSong.songId + "&hl=en";
324 newerSong = constructDownloadInfo(newerSong, 0);
325 return newerSong;
326 }
327
328 you_tube_api::you_tube_api(config_manager* configManagerNew, const snowflake guildIdNew) : you_tube_request_builder{ configManagerNew } {
329 guildId = guildIdNew;
330 }
331
332 void you_tube_api::weFailedToDownloadOrDecode(const song& songNew, std::coroutine_handle<co_routine<void, false>::promise_type> threadHandle, uint64_t recursionDepth) {
333 std::this_thread::sleep_for(1s);
334 if (recursionDepth < 10) {
335 ++recursionDepth;
336 song songNewer = constructDownloadInfo(songNew, 0);
337 downloadAndStreamAudio(songNewer, threadHandle, recursionDepth);
338 } else {
339 discord_core_client::getVoiceConnection(guildId).skip(true);
340 }
341 }
342
343 co_routine<void, false> you_tube_api::downloadAndStreamAudio(const song songNew, std::coroutine_handle<co_routine<void, false>::promise_type> threadHandle,
344 uint64_t currentReconnectTries) {
345 try {
346 areWeWorkingBool.store(true, std::memory_order_release);
347 if (currentReconnectTries == 0) {
348 threadHandle = co_await newThreadAwaitable<void, false>();
349 }
350 if (songNew.type != song_type::YouTube) {
351 message_printer::printError<print_message_type::general>("Failed to have the correct song type.");
352 co_return;
353 }
354 uint64_t intervalCount{ songNew.contentLength / (1024ULL * 1024ULL) + 1ULL };
355 uint64_t remainder{ songNew.contentLength % (1024ULL * 1024ULL) };
356 uint64_t currentStart{};
357 uint64_t currentEnd{ intervalCount > 1 ? (1024ULL * 1024ULL) : remainder };
358 jsonifier::vector<https_workload_data> workloadVector{};
359 for (uint64_t x = 0; x < intervalCount; ++x) {
360 https_workload_data workloadData{ https_workload_type::YouTube_Get_Search_Results };
361 if (songNew.finalDownloadUrls.size() > 0) {
362 if (songNew.finalDownloadUrls.at(0).urlPath.find(".com") != jsonifier::string::npos) {
363 workloadData.baseUrl = songNew.finalDownloadUrls.at(0).urlPath.substr(0, songNew.finalDownloadUrls.at(0).urlPath.find(".com") + 4);
364 }
365 } else {
366 weFailedToDownloadOrDecode(songNew, threadHandle, currentReconnectTries);
367 areWeWorkingBool.store(false, std::memory_order_release);
368 co_return;
369 }
370 workloadData.workloadClass = https_workload_class::Get;
371 workloadData.headersToInsert["User-Agent"] = "com.google.android.you_tube/17.10.35 (Linux; U; Android 12; US) gzip";
372 workloadData.headersToInsert["Connection"] = "Keep-Alive";
373 workloadData.headersToInsert["Host"] = songNew.finalDownloadUrls.at(0).urlPath;
374 workloadData.headersToInsert["Origin"] = "https://music.you_tube.com";
375 workloadData.relativePath = songNew.finalDownloadUrls.at(1).urlPath + "&range=" + jsonifier::toString(currentStart) + "-" + jsonifier::toString(currentEnd);
376 workloadVector.emplace_back(std::move(workloadData));
377 currentStart = currentEnd;
378 currentEnd += x == intervalCount - 2 ? remainder : (1024ULL * 1024ULL);
379 }
380 jsonifier::string_base<uint8_t> buffer{};
381 matroska_demuxer demuxer{};
382 uint64_t index{};
383 while (index < intervalCount || !demuxer.areWeDone() && !threadHandle.promise().stopRequested()) {
384 if (index < intervalCount) {
385 https_response_data result{ submitWorkloadAndGetResult(std::move(workloadVector[index])) };
386 if (result.responseCode != 200) {
387 weFailedToDownloadOrDecode(songNew, threadHandle, currentReconnectTries);
388 areWeWorkingBool.store(false, std::memory_order_release);
389 co_return;
390 }
391 if (result.responseData.size() > 0) {
392 ++index;
393 auto oldSize = buffer.size();
394 buffer.resize(buffer.size() + result.responseData.size());
395 std::memcpy(buffer.data() + oldSize, result.responseData.data(), result.responseData.size());
396 demuxer.writeData({ buffer.data(), buffer.size() });
397 demuxer.proceedDemuxing();
398 }
399 }
400 bool didWeReceive{ true };
401 do {
402 audio_frame_data frameData{};
403 didWeReceive = demuxer.collectFrame(frameData);
404 if (threadHandle.promise().stopRequested()) {
405 areWeWorkingBool.store(false, std::memory_order_release);
406 co_return;
407 }
408 if (frameData.currentSize != 0) {
409 discord_core_client::getSongAPI(guildId).audioDataBuffer.send(std::move(frameData));
410 }
411 } while (didWeReceive);
412 std::this_thread::sleep_for(1ms);
413 }
414 areWeWorkingBool.store(false, std::memory_order_release);
415 discord_core_client::getVoiceConnection(guildId).skip(false);
416 audio_frame_data frameData{};
417 discord_core_client::getSongAPI(guildId).audioDataBuffer.send(std::move(frameData));
418 co_return;
419 } catch (const https_error& error) {
420 message_printer::printError<print_message_type::https>("you_tube_api::downloadAndStreamAudio() error: " + jsonifier::string{ error.what() });
421 weFailedToDownloadOrDecode(songNew, threadHandle, currentReconnectTries);
422 areWeWorkingBool.store(false, std::memory_order_release);
423 }
424 co_return;
425 }
426
427 bool you_tube_api::areWeWorking() {
428 return areWeWorkingBool.load(std::memory_order_acquire);
429 }
430
431 jsonifier::vector<song> you_tube_api::searchForSong(jsonifier::string_view searchQuery, uint64_t limit) {
432 return collectSearchResults(searchQuery, limit);
433 }
434
435 }
436}
The main namespace for the forward-facing interfaces.