#include #include #include #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 opt; std::string const video_url = { "https://www.youtube.com/watch?v=" }; std::string search_url = { "https://www.youtube.com/results?search_query=" }; while ((opt = getopt(argc, argv, "hv")) != -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 << 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; default: std::cout << "ytsearch: try 'ytsearch -h' for more information" << std::endl; return EXIT_FAILURE; } } search_url += argv[1]; CURL *curl; std::string readBuffer; curl = curl_easy_init(); if (!curl) { std::cout << "ytsearch: error in curl initialization" << std::endl; return EXIT_FAILURE; } 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); 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_video = readBuffer.find("WEB_PAGE_TYPE_WATCH", pos_start); if (first_video == std::string::npos or pos_start == 0) { std::cout << error_api << std::endl; return EXIT_FAILURE; } while (first_video > 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 + 15 }; // size of " url":"/watch?v= ", last part of paths size_t pos_end = { readBuffer.find('"', size) }; std::cout << video_url << readBuffer.substr(size, pos_end - size) << std::endl; return EXIT_SUCCESS; }