34namespace DiscordCoreInternal {
36 std::vector<DiscordCoreAPI::Song> YouTubeRequestBuilder::collectSearchResults(
const std::string& searchQuery) {
37 HttpsWorkloadData dataPackage{ HttpsWorkloadType::YouTubeGetSearchResults };
38 dataPackage.baseUrl = this->baseUrl;
39 dataPackage.relativePath =
"/results?search_query=" + DiscordCoreAPI::urlEncode(searchQuery.c_str());
40 dataPackage.workloadClass = HttpsWorkloadClass::Get;
41 HttpsResponseData returnData = this->httpsClient->submitWorkloadAndGetResult(dataPackage);
42 if (returnData.responseCode != 200 && this->configManager->doWePrintHttpsErrorMessages()) {
43 cout << DiscordCoreAPI::shiftToBrightRed() <<
"YouTubeRequestBuilder::collectSearchResults() Error: " << returnData.responseCode
44 << returnData.responseData.c_str() << DiscordCoreAPI::reset() << endl
47 Jsonifier::Value partialSearchResultsJson{};
49 std::vector<DiscordCoreAPI::Song> searchResults{};
50 auto varInitFind = returnData.responseData.find(
"var ytInitialData = ");
51 if (varInitFind != std::string::npos) {
52 std::string newString00 =
"var ytInitialData = ";
53 std::string newString = returnData.responseData.substr(varInitFind + newString00.length());
54 std::string stringSequence =
";</script><script nonce=";
55 newString = newString.substr(0, newString.find(stringSequence));
56 Jsonifier::Parser parser{};
57 partialSearchResultsJson = parser.parseJson(newString);
58 Jsonifier::Value objectContents{};
59 if (partialSearchResultsJson[
"contents"].get(objectContents) == Jsonifier::ErrorCode::Success) {
60 if (objectContents[
"twoColumnSearchResultsRenderer"].get(objectContents) == Jsonifier::ErrorCode::Success) {
61 if (objectContents[
"primaryContents"].get(objectContents) == Jsonifier::ErrorCode::Success) {
62 if (objectContents[
"sectionListRenderer"].get(objectContents) == Jsonifier::ErrorCode::Success) {
63 if (objectContents[
"contents"].get(objectContents) == Jsonifier::ErrorCode::Success) {
64 if (objectContents.at(0).get(objectContents) == Jsonifier::ErrorCode::Success) {
65 if (objectContents[
"itemSectionRenderer"].get(objectContents) == Jsonifier::ErrorCode::Success) {
66 if (objectContents[
"contents"].get(objectContents) == Jsonifier::ErrorCode::Success) {
67 for (
auto iterator: objectContents) {
69 Jsonifier::Value
object{};
70 if (iterator[
"videoRenderer"].get(
object) == Jsonifier::ErrorCode::Success) {
74 searchResult.viewUrl = this->baseUrl +
"/watch?v=" + searchResult.songId +
"&hl=en";
75 if (searchResult.description ==
"" || searchResult.viewUrl ==
"") {
78 searchResults.emplace_back(searchResult);
93 HttpsResponseData responseData{};
95 Jsonifier::Serializer request{};
96 request[
"videoId"] = newSong.songId;
97 request[
"contentCheckOk"] =
true;
98 request[
"racyCheckOk"] =
true;
99 request[
"context"][
"client"][
"clientName"] =
"ANDROID";
100 request[
"context"][
"client"][
"clientScreen"] =
"EMBED";
101 request[
"context"][
"client"][
"clientVersion"] =
"16.46.37";
102 request[
"context"][
"client"][
"hl"] =
"en";
103 request[
"context"][
"client"][
"gl"] =
"US";
104 request[
"context"][
"client"][
"utcOffsetMinutes"] = 0;
105 request[
"context"][
"embedUrl"] =
"https://www.youtube.com";
106 HttpsWorkloadData dataPackage02{ HttpsWorkloadType::YouTubeGetSearchResults };
107 dataPackage02.baseUrl = YouTubeRequestBuilder::baseUrl;
108 dataPackage02.relativePath =
"/youtubei/v1/player?key=" + YouTubeRequestBuilder::apiKey;
109 request.refreshString(DiscordCoreAPI::JsonifierSerializeType::Json);
110 dataPackage02.content = request.operator std::string();
111 dataPackage02.workloadClass = HttpsWorkloadClass::Post;
112 responseData = this->httpsClient->submitWorkloadAndGetResult(dataPackage02);
113 if (responseData.responseCode != 204 && responseData.responseCode != 201 && responseData.responseCode != 200 &&
114 this->configManager->doWePrintHttpsErrorMessages()) {
115 cout << DiscordCoreAPI::shiftToBrightRed()
116 <<
"YouTubeRequestBuilder::constructDownloadInfo() 01 Error: " << responseData.responseCode <<
", "
117 << responseData.responseData << DiscordCoreAPI::reset() << endl
121 Jsonifier::Parser parser{};
122 Jsonifier::Value value{};
123 if (parser.parseJson(responseData.responseData).get(value) == Jsonifier::ErrorCode::Success) {
125 DiscordCoreAPI::YouTubeFormat format{};
127 for (
auto& value:
static_cast<std::vector<DiscordCoreAPI::YouTubeFormat>
>(vector)) {
128 if (value.mimeType.find(
"opus") != std::string::npos) {
129 if (value.audioQuality ==
"AUDIO_QUALITY_LOW") {
133 if (value.audioQuality ==
"AUDIO_QUALITY_MEDIUM") {
137 if (value.audioQuality ==
"AUDIO_QUALITY_HIGH") {
144 newSong.format = format;
147 std::string downloadBaseUrl{};
148 auto httpsFind = newSong.format.downloadUrl.find(
"https://");
149 auto videoPlaybackFind = newSong.format.downloadUrl.find(
"/videoplayback?");
150 if (httpsFind != std::string::npos && videoPlaybackFind != std::string::npos) {
151 std::string newString00 =
"https://";
153 newSong.format.downloadUrl.substr(httpsFind + newString00.length(), videoPlaybackFind - newString00.length());
155 std::string requestNew =
"GET " + newSong.format.downloadUrl +
156 " HTTP/1.1\n\rUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
159 requestNew +=
"Host: " + downloadBaseUrl +
"\n\r\n\r";
160 newSong.finalDownloadUrls.resize(2);
162 downloadUrl01.contentSize = newSong.contentLength;
163 downloadUrl01.urlPath = downloadBaseUrl;
165 downloadUrl02.contentSize = newSong.contentLength;
166 downloadUrl02.urlPath = requestNew;
167 newSong.finalDownloadUrls[0] = downloadUrl01;
168 newSong.finalDownloadUrls[1] = downloadUrl02;
169 newSong.
viewUrl = newSong.firstDownloadUrl;
170 newSong.contentLength = newSong.format.contentLength;
174 if (currentRecursionDepth <= 10) {
175 ++currentRecursionDepth;
176 return this->constructDownloadInfo(newSong, currentRecursionDepth);
178 if (this->configManager->doWePrintHttpsErrorMessages()) {
188 newSong.firstDownloadUrl = this->baseUrl +
"/watch?v=" + newSong.songId +
"&hl=en";
189 newSong = this->constructDownloadInfo(newSong, 0);
193 std::string YouTubeRequestBuilder::collectApiKey() {
194 HttpsWorkloadData dataPackage01{ HttpsWorkloadType::YouTubeGetSearchResults };
195 dataPackage01.baseUrl = YouTubeRequestBuilder::baseUrl;
196 dataPackage01.workloadClass = HttpsWorkloadClass::Get;
197 HttpsResponseData responseData01 = this->httpsClient->submitWorkloadAndGetResult(dataPackage01);
198 std::string apiKey{};
199 if (responseData01.responseData.find(
"\"innertubeApiKey\":\"") != std::string::npos) {
200 std::string newString = responseData01.responseData.substr(
201 responseData01.responseData.find(
"\"innertubeApiKey\":\"") + std::string{
"\"innertubeApiKey\":\"" }.size());
202 std::string apiKeyNew = newString.substr(0, newString.find_first_of(
'"'));
208 YouTubeAPI::YouTubeAPI(DiscordCoreAPI::ConfigManager* configManagerNew, HttpsClient* httpsClientNew,
209 const DiscordCoreAPI::Snowflake guildIdNew) {
210 this->configManager = configManagerNew;
211 this->httpsClient = httpsClientNew;
212 this->guildId =
static_cast<DiscordCoreAPI::Snowflake
>(guildIdNew);
213 if (YouTubeRequestBuilder::apiKey ==
"") {
214 YouTubeRequestBuilder::apiKey = this->collectApiKey();
218 void YouTubeAPI::weFailedToDownloadOrDecode(
const DiscordCoreAPI::Song& newSong, std::stop_token token, int32_t currentReconnectTries) {
219 ++currentReconnectTries;
223 if (currentReconnectTries > 9) {
225 while (DiscordCoreAPI::DiscordCoreClient::getSongAPI(this->guildId)->audioDataBuffer.tryReceive(frameData)) {
228 auto returnValue = DiscordCoreAPI::DiscordCoreClient::getSongAPI(this->guildId);
230 eventData.previousSong = returnValue->getCurrentSong(this->guildId);
232 eventData.wasItAFail =
true;
233 eventData.guildMember = guildMember;
235 DiscordCoreAPI::DiscordCoreClient::getSongAPI(this->guildId)->onSongCompletionEvent(eventData);
237 newerSong = this->collectFinalSong(newerSong);
238 YouTubeAPI::downloadAndStreamAudio(newerSong, token, currentReconnectTries);
242 void YouTubeAPI::downloadAndStreamAudio(
const DiscordCoreAPI::Song& newSong, std::stop_token token, int32_t currentReconnectTries) {
244 std::unique_ptr<WebSocketClient> streamSocket{ std::make_unique<WebSocketClient>(
nullptr, 0,
nullptr) };
245 auto bytesRead{
static_cast<int32_t
>(streamSocket->getBytesRead()) };
246 if (newSong.finalDownloadUrls.size() > 0) {
247 if (!
static_cast<TCPSSLClient*
>(streamSocket.get())
248 ->connect(newSong.finalDownloadUrls[0].urlPath, 443, this->configManager->doWePrintWebSocketErrorMessages(),
250 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
254 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
257 bool areWeDoneHeaders{};
258 int64_t remainingDownloadContentLength{
static_cast<int64_t
>(newSong.contentLength) };
259 int64_t bytesToRead{
static_cast<int64_t
>(this->maxBufferSize) };
260 int64_t bytesSubmittedPrevious{};
261 int64_t bytesReadTotal{};
262 const uint8_t maxReruns{ 200 };
263 uint8_t currentReruns{};
265 uint32_t headerSize{};
266 BuildAudioDecoderData dataPackage{};
267 std::string currentString{};
268 dataPackage.totalFileSize =
static_cast<uint64_t
>(newSong.contentLength);
269 dataPackage.bufferMaxSize = this->maxBufferSize;
270 dataPackage.configManager = this->configManager;
271 std::unique_ptr<AudioDecoder> audioDecoder = std::make_unique<AudioDecoder>(dataPackage);
272 std::string
string = newSong.finalDownloadUrls[1].urlPath;
273 streamSocket->writeData(
string,
true);
274 std::vector<DiscordCoreAPI::AudioFrameData> frames{};
275 streamSocket->processIO(1000);
276 if (!streamSocket->areWeStillConnected()) {
277 audioDecoder.reset(
nullptr);
278 streamSocket->disconnect();
279 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
282 while (remainingDownloadContentLength > 0) {
283 std::this_thread::sleep_for(1ms);
284 if (bytesSubmittedPrevious == bytesReadTotal) {
289 if (currentReruns >= maxReruns) {
292 frameData.currentSize = 0;
293 DiscordCoreAPI::DiscordCoreClient::getSongAPI(this->guildId)->audioDataBuffer.send(std::move(frameData));
294 streamSocket->disconnect();
295 audioDecoder.reset(
nullptr);
296 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
299 bytesSubmittedPrevious = bytesReadTotal;
300 if (audioDecoder->haveWeFailed()) {
301 streamSocket->disconnect();
302 audioDecoder.reset(
nullptr);
303 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
306 if (token.stop_requested()) {
307 streamSocket->disconnect();
308 audioDecoder.reset(
nullptr);
311 if (!areWeDoneHeaders) {
312 remainingDownloadContentLength = newSong.contentLength - bytesReadTotal;
313 streamSocket->processIO(10);
314 if (!streamSocket->areWeStillConnected()) {
315 streamSocket->disconnect();
316 audioDecoder.reset(
nullptr);
317 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
320 if (!token.stop_requested()) {
321 if (streamSocket->areWeStillConnected()) {
322 bytesReadTotal = streamSocket->getBytesRead() - headerSize;
323 std::string streamBufferReal =
static_cast<std::string
>(streamSocket->getInputBuffer());
324 headerSize =
static_cast<int32_t
>(streamBufferReal.size());
327 remainingDownloadContentLength = newSong.contentLength - bytesReadTotal;
328 areWeDoneHeaders =
true;
330 if (token.stop_requested()) {
331 streamSocket->disconnect();
332 audioDecoder.reset(
nullptr);
336 streamSocket->processIO(10);
337 if (!streamSocket->areWeStillConnected()) {
338 streamSocket->disconnect();
339 audioDecoder.reset(
nullptr);
340 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
343 std::string streamBufferReal =
static_cast<std::string
>(streamSocket->getInputBuffer());
344 if (streamBufferReal.size() > 0) {
345 currentString.insert(currentString.end(), streamBufferReal.data(),
346 streamBufferReal.data() + streamBufferReal.size());
347 std::string submissionString{};
348 if (currentString.size() >= this->maxBufferSize) {
349 submissionString.insert(submissionString.begin(), currentString.data(),
350 currentString.data() + this->maxBufferSize);
351 currentString.erase(currentString.begin(), currentString.begin() + this->maxBufferSize);
353 submissionString = std::move(currentString);
354 currentString.clear();
356 bytesReadTotal = streamSocket->getBytesRead();
357 audioDecoder->submitDataForDecoding(std::move(submissionString));
359 audioDecoder->startMe();
360 }
else if (counter > 0) {
361 remainingDownloadContentLength = newSong.contentLength - bytesReadTotal;
362 streamSocket->processIO(10);
363 if (!streamSocket->areWeStillConnected()) {
364 streamSocket->disconnect();
365 audioDecoder.reset(
nullptr);
366 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
369 std::string streamBufferReal =
static_cast<std::string
>(streamSocket->getInputBuffer());
371 if (streamBufferReal.size() > 0) {
372 currentString.insert(currentString.end(), streamBufferReal.data(),
373 streamBufferReal.data() + streamBufferReal.size());
374 while (currentString.size() > 0) {
375 std::string submissionString{};
376 if (currentString.size() >= this->maxBufferSize) {
377 submissionString.insert(submissionString.begin(), currentString.data(),
378 currentString.data() + this->maxBufferSize);
379 currentString.erase(currentString.begin(), currentString.begin() + this->maxBufferSize);
381 submissionString = std::move(currentString);
382 currentString.clear();
384 audioDecoder->submitDataForDecoding(std::move(submissionString));
385 bytesReadTotal = streamSocket->getBytesRead();
388 if (token.stop_requested()) {
389 streamSocket->disconnect();
390 audioDecoder.reset(
nullptr);
395 if (!audioDecoder->getFrame(rawFrame)) {
398 if (rawFrame.currentSize == -5) {
401 if (rawFrame.currentSize > 3) {
402 frames.emplace_back(std::move(rawFrame));
406 for (
auto iterator = frames.begin(); iterator != frames.end();) {
408 DiscordCoreAPI::DiscordCoreClient::getSongAPI(this->guildId)->audioDataBuffer.send(std::move(*iterator));
409 iterator = frames.erase(iterator);
412 if (remainingDownloadContentLength >= this->maxBufferSize) {
413 bytesToRead = this->maxBufferSize;
415 bytesToRead = remainingDownloadContentLength;
421 while (audioDecoder->getFrame(frameData01)) {
423 audioDecoder.reset(
nullptr);
426 frameData.currentSize = 0;
427 DiscordCoreAPI::DiscordCoreClient::getSongAPI(this->guildId)->audioDataBuffer.send(std::move(frameData));
429 if (this->configManager->doWePrintWebSocketErrorMessages()) {
432 this->weFailedToDownloadOrDecode(newSong, token, currentReconnectTries);
436 std::vector<DiscordCoreAPI::Song> YouTubeAPI::searchForSong(
const std::string& searchQuery) {
437 return this->collectSearchResults(searchQuery);
441 return YouTubeRequestBuilder::collectFinalSong(newSong);
444 std::string YouTubeRequestBuilder::apiKey{};
DiscordCoreAPI_Dll void reportException(const std::string ¤tFunctionName, std::source_location location=std::source_location::current())
Prints the current file, line, and column from which the function is being called - typically from wi...
Data structure representing a single GuildMember.
Represents a download Url.
A song from the various platforms.
std::string viewUrl
The url for listening to this Song through a browser.
Snowflake addedByUserId
The User id of the individual who added this Song to the playlist.
SongType type
The type of song.
Song completion event data.
static CoRoutine< Guild > getGuildAsync(GetGuildData dataPackage)
Collects a Guild from the Discord servers.
static GuildMemberData getCachedGuildMember(GetGuildMemberData dataPackage)
Collects a GuildMember from the library's cache.
Represents a single frame of audio data.
AudioFrameType type
The type of audio frame.
uint64_t guildMemberId
GuildMemberId for the sending GuildMember.