#include <iostream> #include <curl/curl.h> #include <getopt.h> #include "config.hpp" /* check version */ #ifndef VERSION #define VERSION "UNKNOWN" #endif static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp){ ((std::string*)userp)->append((char*)contents, size * nmemb); return size * nmemb; } /** * goal: ytsearch -h|-v|search-term * * @example ytsearch "rick astley" * https://www.youtube.com/watch?v=dQw4w9WgXcQ */ int main(int argc, char **argv) { if (argc < 2) { std::cout << "ytsearch: a search term is needed, try 'ytsearch -h' for more information" << std::endl; return EXIT_FAILURE; } int const TYPE_VIDEO = { 0 }; int const TYPE_CHANNEL = { 1 }; int const TYPE_PLAYLIST = { 2 }; std::string const IDS[3] = { "WEB_PAGE_TYPE_WATCH", "WEB_PAGE_TYPE_CHANNEL", "WEB_PAGE_TYPE_PLAYLIST", }; int type = TYPE_VIDEO; int indice_search_terms = 1; int opt; while ((opt = getopt(argc, argv, "hvcpd")) != -1) { switch (opt) { case 'h': std::cout << "Usage: ytsearch [options...] search-term" << std::endl << " -h: show this help and quit" << std::endl << " -v: show version number and quit" << std::endl << " -c: get channel url" << std::endl << " -d: get video url (default)" << std::endl << " -p: get playlist url" << std::endl << std::endl << "Retrieve the url of a youtube video matching the searched terms" << std::endl; return EXIT_SUCCESS; case 'v': std::cout << VERSION << std::endl; return EXIT_SUCCESS; case 'c': type = TYPE_CHANNEL; indice_search_terms++; break; case 'p': type = TYPE_PLAYLIST; indice_search_terms++; break; case 'd': type = TYPE_VIDEO; indice_search_terms++; break; default: std::cout << "ytsearch: try 'ytsearch -h' for more information" << std::endl; return EXIT_FAILURE; } } std::string const base_search_url = { "https://www.youtube.com/results?search_query=" }; std::string const youtube_url = { "https://www.youtube.com/" }; CURL *curl; std::string readBuffer; curl = curl_easy_init(); if (!curl) { std::cout << "ytsearch: error in curl initialization" << std::endl; return EXIT_FAILURE; } std::string search_terms = argv[indice_search_terms]; char* search_url_encoded = curl_easy_escape(curl, search_terms.c_str(), 0); std::string search_url = base_search_url + search_url_encoded; curl_easy_setopt(curl, CURLOPT_URL, search_url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); CURLcode res = { curl_easy_perform(curl) }; curl_easy_cleanup(curl); curl_free(search_url_encoded); if (res != 0) { std::cout << "ytsearch: error when fetching results: " << res << std::endl; return EXIT_FAILURE; } std::string paths[4] = { "navigationEndpoint", "commandMetadata", "webCommandMetadata", "url", }; size_t pos_start = readBuffer.find("ytInitialData"); std::string const error_api = "ytsearch: error, youtube's api changed: update ytsearch"; if (pos_start == std::string::npos or pos_start == 0) { std::cout << error_api << std::endl; return EXIT_FAILURE; } size_t first_item = readBuffer.find(IDS[type].c_str(), pos_start); if (first_item == std::string::npos or pos_start == 0) { std::cout << "ytsearch: error, no item found with the asked type" << std::endl; return EXIT_FAILURE; } while (first_item > pos_start + 60) // type is defined a little after url, we want a video { for (std::string name : paths) { pos_start = { readBuffer.find(name, pos_start) }; if (pos_start == std::string::npos or pos_start == 0) { std::cout << error_api << std::endl; return EXIT_FAILURE; } } } size_t size = { pos_start + 7 }; // size of " url":"/ ", last part of paths size_t pos_end = { readBuffer.find('"', size) }; std::cout << youtube_url << readBuffer.substr(size, pos_end - size) << std::endl; return EXIT_SUCCESS; }