From 3c9ca2c97ae7a2b39bfe5c5e8a9d7941f9fb1525 Mon Sep 17 00:00:00 2001
From: dec05eba <dec05eba@protonmail.com>
Date: Thu, 8 Aug 2019 04:13:03 +0200
Subject: Youtube: use real search for search suggestions (better results,
 thumbnails, directly to result)

---
 README.md                 |   4 +-
 include/QuickMedia.hpp    |   1 -
 include/SearchBar.hpp     |   1 +
 plugins/Manganelo.hpp     |   6 ++-
 plugins/Plugin.hpp        |   5 +-
 plugins/Youtube.hpp       |   8 ++--
 src/QuickMedia.cpp        |  27 +++++++----
 src/SearchBar.cpp         |   3 +-
 src/plugins/Manganelo.cpp |   4 +-
 src/plugins/Plugin.cpp    |   6 +++
 src/plugins/Youtube.cpp   | 117 +++++++++++++++++++++++-----------------------
 11 files changed, 101 insertions(+), 81 deletions(-)

diff --git a/README.md b/README.md
index cce411a..14ea3ec 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # QuickMedia
 Native clients of websites with fast access to what you want to see. [Demo with manga](https://beta.lbry.tv/quickmedia_manga-2019-08-05_21.20.46/7).
-Press ctrl+t to when hovering over a manga chapter to start tracking manga after that chapter. This only works if AutoMedia is installed and
+Press `ctrl + t` when hovering over a manga chapter to start tracking manga after that chapter. This only works if AutoMedia is installed and
 accessible in PATH environment variable.
 # Dependencies
 ## Compile
@@ -23,4 +23,4 @@ until all subtitles have been downloaded and loaded.
 Figure out why memory usage doesn't drop much when killing the video player. Is it a bug in proprietary nvidia drivers on gnu/linux?\
 Add grid-view when thumbnails are visible.\
 Add scrollbar.\
-Add option to scale image to window size.
\ No newline at end of file
+Add option to scale image to window size.
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 2ab4733..7534439 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -35,7 +35,6 @@ namespace QuickMedia {
         std::unique_ptr<SearchBar> search_bar;
         Page current_page;
         // TODO: Combine these
-        std::string video_url;
         std::string images_url;
         std::string content_title;
         std::string content_url;
diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp
index c9f75f0..f1ac3fd 100644
--- a/include/SearchBar.hpp
+++ b/include/SearchBar.hpp
@@ -23,6 +23,7 @@ namespace QuickMedia {
 
         TextUpdateCallback onTextUpdateCallback;
         TextSubmitCallback onTextSubmitCallback;
+        int text_autosearch_delay;
     private:
         sf::Text text;
         sf::RectangleShape background;
diff --git a/plugins/Manganelo.hpp b/plugins/Manganelo.hpp
index 01311e1..5720199 100644
--- a/plugins/Manganelo.hpp
+++ b/plugins/Manganelo.hpp
@@ -5,12 +5,14 @@
 namespace QuickMedia {
     class Manganelo : public Plugin {
     public:
-        SearchResult search(const std::string &url, std::vector<std::unique_ptr<BodyItem>> &result_items, Page &next_page) override;
+        SearchResult search(const std::string &url, std::vector<std::unique_ptr<BodyItem>> &result_items) override;
         SuggestionResult update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) override;
         ImageResult get_image_by_index(const std::string &url, int index, std::string &image_data);
         ImageResult get_number_of_images(const std::string &url, int &num_images);
         bool search_suggestions_has_thumbnails() const override { return true; }
-        bool search_results_has_thumbnails() const override { return true; }
+        bool search_results_has_thumbnails() const override { return false; }
+        int get_search_delay() const override { return 150; }
+        Page get_page_after_search() const override { return Page::EPISODE_LIST; }
     private:
         // Caches url. If the same url is requested multiple times then the cache is used
         ImageResult get_image_urls_for_chapter(const std::string &url);
diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp
index 3df53ca..54d49e5 100644
--- a/plugins/Plugin.hpp
+++ b/plugins/Plugin.hpp
@@ -44,11 +44,14 @@ namespace QuickMedia {
     public:
         virtual ~Plugin() = default;
 
-        virtual SearchResult search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items, Page &next_page) = 0;
+        virtual SearchResult search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items);
         virtual SuggestionResult update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items);
         virtual std::vector<std::unique_ptr<BodyItem>> get_related_media(const std::string &url);
         virtual bool search_suggestions_has_thumbnails() const = 0;
         virtual bool search_results_has_thumbnails() const = 0;
+        virtual int get_search_delay() const = 0;
+        virtual bool search_suggestion_is_search() const { return false; }
+        virtual Page get_page_after_search() const = 0;
     protected:
         std::string url_param_encode(const std::string &param) const;
     };
diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp
index e8cc2c2..64a0d6b 100644
--- a/plugins/Youtube.hpp
+++ b/plugins/Youtube.hpp
@@ -5,10 +5,12 @@
 namespace QuickMedia {
     class Youtube : public Plugin {
     public:
-        SearchResult search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items, Page &next_page) override;
         SuggestionResult update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) override;
         std::vector<std::unique_ptr<BodyItem>> get_related_media(const std::string &url) override;
-        bool search_suggestions_has_thumbnails() const override { return false; }
-        bool search_results_has_thumbnails() const override { return true; }
+        bool search_suggestions_has_thumbnails() const override { return true; }
+        bool search_results_has_thumbnails() const override { return false; }
+        int get_search_delay() const override { return 250; }
+        bool search_suggestion_is_search() const override { return true; }
+        Page get_page_after_search() const override { return Page::VIDEO_CONTENT; }
     };
 }
\ No newline at end of file
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index d0a0ee7..856fc73 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -41,7 +41,7 @@ namespace QuickMedia {
         delete current_plugin;
     }
 
-    static SearchResult search_selected_suggestion(Body *body, Plugin *plugin, Page &next_page, std::string &selected_title, std::string &selected_url) {
+    static SearchResult search_selected_suggestion(Body *body, Plugin *plugin, std::string &selected_title, std::string &selected_url) {
         BodyItem *selected_item = body->get_selected();
         if(!selected_item)
             return SearchResult::ERR;
@@ -49,7 +49,7 @@ namespace QuickMedia {
         selected_title = selected_item->title;
         selected_url = selected_item->url;
         body->clear_items();
-        SearchResult search_result = plugin->search(!selected_url.empty() ? selected_url : selected_title, body->items, next_page);
+        SearchResult search_result = plugin->search(!selected_url.empty() ? selected_url : selected_title, body->items);
         body->reset_selected();
         return search_result;
     }
@@ -84,6 +84,8 @@ namespace QuickMedia {
             return -1;
         }
 
+        search_bar->text_autosearch_delay = current_plugin->get_search_delay();
+
         while(window.isOpen()) {
             switch(current_page) {
                 case Page::EXIT:
@@ -93,10 +95,12 @@ namespace QuickMedia {
                     body->draw_thumbnails = current_plugin->search_suggestions_has_thumbnails();
                     search_suggestion_page();
                     break;
+#if 0
                 case Page::SEARCH_RESULT:
                     body->draw_thumbnails = current_plugin->search_results_has_thumbnails();
                     search_result_page();
                     break;
+#endif
                 case Page::VIDEO_CONTENT:
                     body->draw_thumbnails = false;
                     video_content_page();
@@ -201,8 +205,8 @@ namespace QuickMedia {
         };
 
         search_bar->onTextSubmitCallback = [this](const std::string &text) {
-            Page next_page;
-            if(search_selected_suggestion(body, current_plugin, next_page, content_title, content_url) == SearchResult::OK) {
+            Page next_page = current_plugin->get_page_after_search();
+            if(search_selected_suggestion(body, current_plugin, content_title, content_url) == SearchResult::OK) {
                 if(next_page == Page::EPISODE_LIST) {
                     Path content_storage_dir = get_storage_dir().join("manga");
                     if(create_directory_recursive(content_storage_dir) != 0) {
@@ -261,6 +265,7 @@ namespace QuickMedia {
     }
 
     void Program::search_result_page() {
+        #if 0
         search_bar->onTextUpdateCallback = [this](const std::string &text) {
             body->filter_search_fuzzy(text);
             body->clamp_selection();
@@ -307,23 +312,24 @@ namespace QuickMedia {
             search_bar->draw(window);
             window.display();
         }
+        #endif
     }
 
     void Program::video_content_page() {
         search_bar->onTextUpdateCallback = nullptr;
         search_bar->onTextSubmitCallback = nullptr;
 
-        watched_videos.insert(video_url);
+        watched_videos.insert(content_url);
         std::unique_ptr<VideoPlayer> video_player = nullptr;
         try {
-            printf("Play video: %s\n", video_url.c_str());
-            video_player.reset(new VideoPlayer(window, window_size.x, window_size.y, video_url.c_str()));
+            printf("Play video: %s\n", content_url.c_str());
+            video_player.reset(new VideoPlayer(window, window_size.x, window_size.y, content_url.c_str()));
         } catch(VideoInitializationException &e) {
             show_notification("Video player", "Failed to create video player", Urgency::CRITICAL);
             video_player = nullptr;
         }
 
-        std::vector<std::unique_ptr<BodyItem>> related_media = current_plugin->get_related_media(video_url);
+        std::vector<std::unique_ptr<BodyItem>> related_media = current_plugin->get_related_media(content_url);
         bool reload = false;
 
         if(video_player) {
@@ -342,8 +348,8 @@ namespace QuickMedia {
                 if(new_video_url.empty())
                     return;
 
-                video_url = std::move(new_video_url);
-                related_media = current_plugin->get_related_media(video_url);
+                content_url = std::move(new_video_url);
+                related_media = current_plugin->get_related_media(content_url);
                 // TODO: This doesn't seem to work correctly right now, it causes video to become black when changing video (context reset bug).
                 //video_player->load_file(video_url);
                 reload = true;
@@ -443,6 +449,7 @@ namespace QuickMedia {
                 }
             }
 
+            // TODO: This code is duplicated in many places. Handle it in one place.
             if(resized) {
                 search_bar->onWindowResize(window_size);
 
diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp
index f7ac48d..1094883 100644
--- a/src/SearchBar.cpp
+++ b/src/SearchBar.cpp
@@ -12,6 +12,7 @@ namespace QuickMedia {
     SearchBar::SearchBar(sf::Font &font) :
         onTextUpdateCallback(nullptr),
         onTextSubmitCallback(nullptr),
+        text_autosearch_delay(0),
         text("Search...", font, 18), 
         show_placeholder(true),
         updated_search(false)
@@ -29,7 +30,7 @@ namespace QuickMedia {
     }
 
     void SearchBar::update() {
-        if(updated_search && time_since_search_update.getElapsedTime().asMilliseconds() >= 150) {
+        if(updated_search && time_since_search_update.getElapsedTime().asMilliseconds() >= text_autosearch_delay) {
             updated_search = false;
             sf::String str = text.getString();
             if(show_placeholder)
diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp
index 3c6dd5b..b1f02a3 100644
--- a/src/plugins/Manganelo.cpp
+++ b/src/plugins/Manganelo.cpp
@@ -3,9 +3,7 @@
 #include <json/reader.h>
 
 namespace QuickMedia {
-    SearchResult Manganelo::search(const std::string &url, std::vector<std::unique_ptr<BodyItem>> &result_items, Page &next_page) {
-        next_page = Page::EPISODE_LIST;
-
+    SearchResult Manganelo::search(const std::string &url, std::vector<std::unique_ptr<BodyItem>> &result_items) {
         std::string website_data;
         if(download_to_string(url, website_data) != DownloadResult::OK)
             return SearchResult::NET_ERR;
diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp
index 2367cb3..86f5d7d 100644
--- a/src/plugins/Plugin.cpp
+++ b/src/plugins/Plugin.cpp
@@ -10,6 +10,12 @@ static int accumulate_string(char *data, int size, void *userdata) {
 }
 
 namespace QuickMedia {
+    SearchResult Plugin::search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) {
+        (void)text;
+        (void)result_items;
+        return SearchResult::OK;
+    }
+
     SuggestionResult Plugin::update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) {
         (void)text;
         (void)result_items;
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index 40d545c..a5670ec 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -12,64 +12,6 @@ namespace QuickMedia {
         return strstr(str, substr);
     }
 
-    SearchResult Youtube::search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items, Page &next_page) {
-        next_page = Page::SEARCH_RESULT;
-        std::string url = "https://youtube.com/results?search_query=";
-        url += url_param_encode(text);
-
-        std::string website_data;
-        if(download_to_string(url, website_data) != DownloadResult::OK)
-            return SearchResult::NET_ERR;
-
-        struct ItemData {
-            std::vector<std::unique_ptr<BodyItem>> *result_items;
-            size_t index;
-        };
-        ItemData item_data = { &result_items, 0 };
-
-        QuickMediaHtmlSearch html_search;
-        int result = quickmedia_html_search_init(&html_search, website_data.c_str());
-        if(result != 0)
-            goto cleanup;
-
-        result = quickmedia_html_find_nodes_xpath(&html_search, "//h3[class=\"yt-lockup-title\"]/a",
-            [](QuickMediaHtmlNode *node, void *userdata) {
-                auto *result_items = (std::vector<std::unique_ptr<BodyItem>>*)userdata;
-                const char *href = quickmedia_html_node_get_attribute_value(node, "href");
-                const char *title = quickmedia_html_node_get_attribute_value(node, "title");
-                // Checking for watch?v helps skipping ads
-                if(href && title && begins_with(href, "/watch?v=")) {
-                    auto item = std::make_unique<BodyItem>(strip(title));
-                    item->url = std::string("https://www.youtube.com") + href;
-                    result_items->push_back(std::move(item));
-                }
-            }, &result_items);
-        if(result != 0)
-            goto cleanup;
-
-        result = quickmedia_html_find_nodes_xpath(&html_search, "//span[class=\"yt-thumb-simple\"]//img",
-            [](QuickMediaHtmlNode *node, void *userdata) {
-                ItemData *item_data = (ItemData*)userdata;
-                if(item_data->index >= item_data->result_items->size())
-                    return;
-
-                const char *src = quickmedia_html_node_get_attribute_value(node, "src");
-                const char *data_thumb = quickmedia_html_node_get_attribute_value(node, "data-thumb");
-
-                if(src && contains(src, "i.ytimg.com/")) {
-                    (*item_data->result_items)[item_data->index]->thumbnail_url = src;
-                    ++item_data->index;
-                } else if(data_thumb && contains(data_thumb, "i.ytimg.com/")) {
-                    (*item_data->result_items)[item_data->index]->thumbnail_url = data_thumb;
-                    ++item_data->index;
-                }
-            }, &item_data);
-
-        cleanup:
-        quickmedia_html_search_deinit(&html_search);
-        return result == 0 ? SearchResult::OK : SearchResult::ERR;
-    }
-
     static void iterate_suggestion_result(const Json::Value &value, std::vector<std::unique_ptr<BodyItem>> &result_items, int &iterate_count) {
         ++iterate_count;
         if(value.isArray()) {
@@ -83,7 +25,11 @@ namespace QuickMedia {
         }
     }
 
+    // TODO: Speed this up by using string.find instead of parsing html
     SuggestionResult Youtube::update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) {
+        // Keep this for backup. This is using search suggestion the same way youtube does it, but the results
+        // are not as good as doing an actual search.
+        #if 0
         std::string url = "https://clients1.google.com/complete/search?client=youtube&hl=en&gs_rn=64&gs_ri=youtube&ds=yt&cp=7&gs_id=x&q=";
         url += url_param_encode(text);
 
@@ -125,6 +71,61 @@ namespace QuickMedia {
         if(!found_search_text)
             result_items.insert(result_items.begin(), std::make_unique<BodyItem>(text));
         return SuggestionResult::OK;
+        #endif
+        std::string url = "https://youtube.com/results?search_query=";
+        url += url_param_encode(text);
+
+        std::string website_data;
+        if(download_to_string(url, website_data) != DownloadResult::OK)
+            return SuggestionResult::NET_ERR;
+
+        struct ItemData {
+            std::vector<std::unique_ptr<BodyItem>> *result_items;
+            size_t index;
+        };
+        ItemData item_data = { &result_items, 0 };
+
+        QuickMediaHtmlSearch html_search;
+        int result = quickmedia_html_search_init(&html_search, website_data.c_str());
+        if(result != 0)
+            goto cleanup;
+
+        result = quickmedia_html_find_nodes_xpath(&html_search, "//h3[class=\"yt-lockup-title\"]/a",
+            [](QuickMediaHtmlNode *node, void *userdata) {
+                auto *result_items = (std::vector<std::unique_ptr<BodyItem>>*)userdata;
+                const char *href = quickmedia_html_node_get_attribute_value(node, "href");
+                const char *title = quickmedia_html_node_get_attribute_value(node, "title");
+                // Checking for watch?v helps skipping ads
+                if(href && title && begins_with(href, "/watch?v=")) {
+                    auto item = std::make_unique<BodyItem>(strip(title));
+                    item->url = std::string("https://www.youtube.com") + href;
+                    result_items->push_back(std::move(item));
+                }
+            }, &result_items);
+        if(result != 0)
+            goto cleanup;
+
+        result = quickmedia_html_find_nodes_xpath(&html_search, "//span[class=\"yt-thumb-simple\"]//img",
+            [](QuickMediaHtmlNode *node, void *userdata) {
+                ItemData *item_data = (ItemData*)userdata;
+                if(item_data->index >= item_data->result_items->size())
+                    return;
+
+                const char *src = quickmedia_html_node_get_attribute_value(node, "src");
+                const char *data_thumb = quickmedia_html_node_get_attribute_value(node, "data-thumb");
+
+                if(src && contains(src, "i.ytimg.com/")) {
+                    (*item_data->result_items)[item_data->index]->thumbnail_url = src;
+                    ++item_data->index;
+                } else if(data_thumb && contains(data_thumb, "i.ytimg.com/")) {
+                    (*item_data->result_items)[item_data->index]->thumbnail_url = data_thumb;
+                    ++item_data->index;
+                }
+            }, &item_data);
+
+        cleanup:
+        quickmedia_html_search_deinit(&html_search);
+        return result == 0 ? SuggestionResult::OK : SuggestionResult::ERR;
     }
 
     std::vector<std::unique_ptr<BodyItem>> Youtube::get_related_media(const std::string &url) {
-- 
cgit v1.2.3-70-g09d2