From b6b972e2dae816a8f0686f4986029a5ed50e592c Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 4 Nov 2020 01:30:38 +0100 Subject: Add channels page to related videos menu, fix related videos menu broken after video failing to load --- README.md | 2 +- TODO | 3 +- include/QuickMedia.hpp | 13 +- plugins/ImageBoard.hpp | 17 +- plugins/Manga.hpp | 7 - plugins/Matrix.hpp | 15 +- plugins/Page.hpp | 29 +++- plugins/Pornhub.hpp | 22 +-- plugins/Youtube.hpp | 27 ++-- src/QuickMedia.cpp | 378 +++++++++++++++++++++++---------------------- src/plugins/ImageBoard.cpp | 6 +- src/plugins/Page.cpp | 5 - src/plugins/Pornhub.cpp | 20 ++- src/plugins/Youtube.cpp | 51 +++++- 14 files changed, 340 insertions(+), 255 deletions(-) diff --git a/README.md b/README.md index 3376f29..c0cbb97 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Press `Enter` (aka `Return`) to select the item.\ Press `ESC` to go back to the previous menu.\ Press `Ctrl + F` to switch between window mode and fullscreen mode when watching a video.\ Press `Space` to pause/unpause a video.\ -Press `Ctrl + R` to show/hide related videos menu when watching a video.\ +Press `Ctrl + R` to show related/channels video menu when watching a video (if supported).\ 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.\ Press `Backspace` to return to the preview item when reading replies in image board threads.\ Press `R` to paste the post number of the selected post into the post field (image boards).\ diff --git a/TODO b/TODO index 7022594..4a64fa9 100644 --- a/TODO +++ b/TODO @@ -119,7 +119,6 @@ Show in room tags list when there is a message in any of the rooms in the tag. Apply current search filter when adding new rooms to the room list. Cancel video download when pressing escape or closing window (important in matrix). Support webp. -Reload history/recommendations after closing a video. Show images while they download by showing them as scanlines starting from the top. Needed for slow websites such as 4chan. Use curl parallel download instead of downloading with multiple threads. Handle matrix groups? (which also contains join, invite, leave...). @@ -138,4 +137,4 @@ Handle matrix token being invalidated while running. Update upload limit if its updated on the server (can it be updated while the server is running?). Apply search filter when updating rooms (including when switching from cache to server response sync data). Editing a reply removes reply formatting (both in body and formatted_body). Element also does this when you edit a reply twice. This breaks element mobile that is unable to display replied-to messages without correct formatting (doesn't fetch the replied-to message). -Implement m.room.tombstone \ No newline at end of file +Implement m.room.tombstone. \ No newline at end of file diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 0641228..0d4b384 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -26,6 +26,7 @@ namespace QuickMedia { class ImageBoardThreadPage; class RoomData; class MatrixChatPage; + class VideoPage; enum class ImageViewMode { SINGLE, @@ -63,10 +64,14 @@ namespace QuickMedia { void set_go_to_previous_page(); TaskResult run_task_with_loading_screen(std::function callback); + + const char* get_plugin_name() const; + void manga_get_watch_history(const char *plugin_name, BodyItems &history_items); + void youtube_get_watch_history(BodyItems &history_items); private: void base_event_handler(sf::Event &event, PageType previous_page, Body *body, SearchBar *search_bar, bool handle_key_press = true, bool handle_searchbar = true); - void page_loop(std::vector &tabs); - void video_content_page(Page *page, std::string video_url, std::string video_title, bool download_if_streaming_fails); + void page_loop(std::vector &tabs, int start_tab_index = 0, std::function after_submit_handler = nullptr); + void video_content_page(VideoPage *video_page, std::string video_url, std::string video_title, bool download_if_streaming_fails); // Returns -1 to go to previous chapter, 0 to stay on same chapter and 1 to go to next chapter int image_page(MangaImagesPage *images_page, Body *chapters_body); void image_continuous_page(MangaImagesPage *images_page); @@ -88,12 +93,10 @@ namespace QuickMedia { // Returns PageType::EXIT if empty PageType pop_page_stack(); - void manga_get_watch_history(const char *plugin_name, BodyItems &history_items); - void youtube_get_watch_history(BodyItems &history_items); Json::Value load_video_history_json(); Json::Value load_recommended_json(); - void save_recommendations_from_related_videos(const std::string &video_url, const std::string &video_title, const Body *related_media_body); + void save_recommendations_from_related_videos(const std::string &video_url, const std::string &video_title, const BodyItems &related_media_body_items); private: enum class UpscaleImageAction { NO, diff --git a/plugins/ImageBoard.hpp b/plugins/ImageBoard.hpp index 2d235ec..c65b269 100644 --- a/plugins/ImageBoard.hpp +++ b/plugins/ImageBoard.hpp @@ -10,20 +10,17 @@ namespace QuickMedia { ERR }; - class ImageBoardThreadPage : public Page { + class ImageBoardThreadPage : public VideoPage { public: - ImageBoardThreadPage(Program *program, std::string board_id, std::string thread_id, std::vector cached_media_urls) : Page(program), board_id(std::move(board_id)), thread_id(std::move(thread_id)), cached_media_urls(std::move(cached_media_urls)) {} + ImageBoardThreadPage(Program *program, std::string board_id, std::string thread_id, std::vector cached_media_urls) : VideoPage(program), board_id(std::move(board_id)), thread_id(std::move(thread_id)), cached_media_urls(std::move(cached_media_urls)) {} const char* get_title() const override { return ""; } - PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { - (void)title; - (void)url; - (void)result_tabs; - return PluginResult::ERR; - } - PageTypez get_type() const override { return PageTypez::IMAGE_BOARD_THREAD; } - virtual BodyItems get_related_media(const std::string &url) override; + BodyItems get_related_media(const std::string &url, std::string &channel_url) override; + std::unique_ptr create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override; + std::unique_ptr create_channels_page(Program*, const std::string&) override { + return nullptr; + } virtual PluginResult login(const std::string &token, const std::string &pin, std::string &response_msg); virtual PostResult post_comment(const std::string &captcha_id, const std::string &comment) = 0; virtual const std::string& get_pass_id(); diff --git a/plugins/Manga.hpp b/plugins/Manga.hpp index cd0ab77..23e8dd0 100644 --- a/plugins/Manga.hpp +++ b/plugins/Manga.hpp @@ -17,13 +17,6 @@ namespace QuickMedia { MangaImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url) : Page(program), manga_name(std::move(manga_name)), chapter_name(std::move(chapter_name)), url(std::move(url)) {} virtual ~MangaImagesPage() = default; const char* get_title() const override { return chapter_name.c_str(); } - PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { - (void)title; - (void)url; - (void)result_tabs; - return PluginResult::OK; - } - PageTypez get_type() const override { return PageTypez::MANGA_IMAGES; } virtual ImageResult get_number_of_images(int &num_images) = 0; diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 20ad7da..e7131b0 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -356,17 +356,16 @@ namespace QuickMedia { }; // Dummy, only play one video. TODO: Play all videos in room, as related videos? - class MatrixVideoPage : public Page { + class MatrixVideoPage : public VideoPage { public: - MatrixVideoPage(Program *program) : Page(program) {} + MatrixVideoPage(Program *program) : VideoPage(program) {} const char* get_title() const override { return ""; } - PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { - (void)title; - (void)url; - (void)result_tabs; - return PluginResult::ERR; + std::unique_ptr create_related_videos_page(Program*, const std::string&, const std::string&) override { + return nullptr; + } + std::unique_ptr create_channels_page(Program*, const std::string&) override { + return nullptr; } - PageTypez get_type() const override { return PageTypez::VIDEO; } }; class MatrixChatPage : public Page { diff --git a/plugins/Page.hpp b/plugins/Page.hpp index 5f5af60..28c2bd6 100644 --- a/plugins/Page.hpp +++ b/plugins/Page.hpp @@ -29,14 +29,16 @@ namespace QuickMedia { virtual SearchResult search(const std::string &str, BodyItems &result_items) { (void)str; (void)result_items; return SearchResult::ERR; } // Return empty |result_tabs| and PluginResult::OK to do nothing; which is useful for implementing custom actions on item submit - virtual PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) = 0; + virtual PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) { + (void)title; + (void)url; + (void)result_tabs; + return PluginResult::ERR; + } // Note: If pagination is done by fetching the next page until we get to |page|, then the "current page" should be reset everytime |search| is called. // Note: the first page is 0 virtual PluginResult get_page(const std::string &str, int page, BodyItems &result_items) { (void)str; (void)page; (void)result_items; return PluginResult::OK; } - // TODO: Move to a subclass called VideoPage - virtual BodyItems get_related_media(const std::string &url); - DownloadResult download_json(Json::Value &result, const std::string &url, std::vector additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr); virtual PageTypez get_type() const { return PageTypez::REGULAR; } @@ -81,7 +83,26 @@ namespace QuickMedia { class LazyFetchPage : public Page { public: LazyFetchPage(Program *program) : Page(program) {} + bool search_is_filter() override { return true; } bool is_lazy_fetch_page() const override { return true; } virtual PluginResult lazy_fetch(BodyItems &result_items) = 0; }; + + class RelatedVideosPage : public Page { + public: + RelatedVideosPage(Program *program) : Page(program) {} + const char* get_title() const override { return "Related videos"; } + }; + + class VideoPage : public Page { + public: + VideoPage(Program *program) : Page(program) {} + virtual PageTypez get_type() const override { return PageTypez::VIDEO; } + virtual BodyItems get_related_media(const std::string &url, std::string &channel_url) { (void)url; (void)channel_url; return {}; } + virtual std::unique_ptr create_search_page(Program *program, int &search_delay) { (void)program; (void)search_delay; return nullptr; } + // Return nullptr if the service doesn't support related videos page + virtual std::unique_ptr create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) = 0; + // Return nullptr if the service doesn't support channels page + virtual std::unique_ptr create_channels_page(Program *program, const std::string &channel_url) = 0; + }; } \ No newline at end of file diff --git a/plugins/Pornhub.hpp b/plugins/Pornhub.hpp index 74fb00e..5c3f835 100644 --- a/plugins/Pornhub.hpp +++ b/plugins/Pornhub.hpp @@ -13,17 +13,21 @@ namespace QuickMedia { PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override; }; - class PornhubVideoPage : public Page { + class PornhubRelatedVideosPage : public RelatedVideosPage { public: - PornhubVideoPage(Program *program) : Page(program) {} + PornhubRelatedVideosPage(Program *program) : RelatedVideosPage(program) {} + PluginResult submit(const std::string&, const std::string&, std::vector &result_tabs) override; + }; + + class PornhubVideoPage : public VideoPage { + public: + PornhubVideoPage(Program *program) : VideoPage(program) {} const char* get_title() const override { return ""; } - PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { - (void)title; - (void)url; - (void)result_tabs; - return PluginResult::ERR; + BodyItems get_related_media(const std::string &url, std::string &channel_url) override; + std::unique_ptr create_search_page(Program *program, int &search_delay) override; + std::unique_ptr create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override; + std::unique_ptr create_channels_page(Program*, const std::string&) override { + return nullptr; } - BodyItems get_related_media(const std::string &url) override; - PageTypez get_type() const override { return PageTypez::VIDEO; } }; } \ No newline at end of file diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index e2dd201..1a66e5c 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -21,12 +21,13 @@ namespace QuickMedia { std::unordered_set added_videos; }; - class YoutubeChannelPage : public Page { + class YoutubeChannelPage : public LazyFetchPage { public: - YoutubeChannelPage(Program *program, std::string url, std::string continuation_token, std::string title) : Page(program), url(std::move(url)), continuation_token(std::move(continuation_token)), title(std::move(title)) {} + YoutubeChannelPage(Program *program, std::string url, std::string continuation_token, std::string title) : LazyFetchPage(program), url(std::move(url)), continuation_token(std::move(continuation_token)), title(std::move(title)) {} const char* get_title() const override { return title.c_str(); } PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override; + PluginResult lazy_fetch(BodyItems &result_items) override; std::unordered_set added_videos; private: @@ -38,17 +39,19 @@ namespace QuickMedia { int current_page = 0; }; - class YoutubeVideoPage : public Page { + class YoutubeRelatedVideosPage : public RelatedVideosPage { public: - YoutubeVideoPage(Program *program) : Page(program) {} + YoutubeRelatedVideosPage(Program *program) : RelatedVideosPage(program) {} + PluginResult submit(const std::string&, const std::string&, std::vector &result_tabs) override; + }; + + class YoutubeVideoPage : public VideoPage { + public: + YoutubeVideoPage(Program *program) : VideoPage(program) {} const char* get_title() const override { return ""; } - PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { - (void)title; - (void)url; - (void)result_tabs; - return PluginResult::ERR; - } - BodyItems get_related_media(const std::string &url) override; - PageTypez get_type() const override { return PageTypez::VIDEO; } + BodyItems get_related_media(const std::string &url, std::string &channel_url) override; + std::unique_ptr create_search_page(Program *program, int &search_delay) override; + std::unique_ptr create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override; + std::unique_ptr create_channels_page(Program *program, const std::string &channel_url) override; }; } \ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 7d7e97f..bd19f2a 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -169,15 +169,35 @@ static sf::Color interpolate_colors(sf::Color source, sf::Color target, double p } namespace QuickMedia { + enum class HistoryType { + YOUTUBE, + MANGA + }; + class HistoryPage : public Page { public: - HistoryPage(Program *program, Page *search_page) : Page(program), search_page(search_page) {} + HistoryPage(Program *program, Page *search_page, SearchBar *search_bar, HistoryType history_type) : + Page(program), search_page(search_page), search_bar(search_bar), history_type(history_type) {} const char* get_title() const override { return "History"; } PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override { return search_page->submit(title, url, result_tabs); } + void on_navigate_to_page(Body *body) override { + body->clear_items(); + switch(history_type) { + case HistoryType::YOUTUBE: + program->youtube_get_watch_history(body->items); + break; + case HistoryType::MANGA: + program->manga_get_watch_history(program->get_plugin_name(), body->items); + break; + } + body->filter_search_fuzzy(search_bar->get_text()); + } private: Page *search_page; + SearchBar *search_bar; + HistoryType history_type; }; class RecommendedPage : public Page { @@ -533,22 +553,25 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(search_body), std::make_unique(this), create_search_bar("Search...", 200)}); auto history_body = create_body(); - manga_get_watch_history(plugin_name, history_body->items); - tabs.push_back(Tab{std::move(history_body), std::make_unique(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); + auto history_page = std::make_unique(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); + tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "mangatown") == 0) { auto search_body = create_body(); tabs.push_back(Tab{std::move(search_body), std::make_unique(this), create_search_bar("Search...", 200)}); auto history_body = create_body(); - manga_get_watch_history(plugin_name, history_body->items); - tabs.push_back(Tab{std::move(history_body), std::make_unique(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); + auto history_page = std::make_unique(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); + tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "mangadex") == 0) { auto search_body = create_body(); tabs.push_back(Tab{std::move(search_body), std::make_unique(this), create_search_bar("Search...", 300)}); auto history_body = create_body(); - manga_get_watch_history(plugin_name, history_body->items); - tabs.push_back(Tab{std::move(history_body), std::make_unique(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); + auto history_page = std::make_unique(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); + tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "nyaa.si") == 0) { auto category_page = std::make_unique(this); auto categories_body = create_body(); @@ -573,8 +596,9 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(search_body), std::make_unique(this), create_search_bar("Search...", 350)}); auto history_body = create_body(); - youtube_get_watch_history(history_body->items); - tabs.push_back(Tab{std::move(history_body), std::make_unique(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); + auto history_page = std::make_unique(this, tabs.front().page.get(), search_bar.get(), HistoryType::YOUTUBE); + tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); auto recommended_body = create_body(); fill_recommended_items_from_json(plugin_name, load_recommended_json(), recommended_body->items); @@ -890,7 +914,7 @@ namespace QuickMedia { } } - void Program::page_loop(std::vector &tabs) { + void Program::page_loop(std::vector &tabs, int start_tab_index, std::function after_submit_handler) { if(tabs.empty()) { show_notification("QuickMedia", "No tabs provided!", Urgency::CRITICAL); return; @@ -947,96 +971,106 @@ namespace QuickMedia { sf::Vertex gradient_points[4]; sf::Text tab_text("", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); - int selected_tab = 0; + int selected_tab = std::min(std::max(0, start_tab_index), (int)tabs.size() - 1); bool loop_running = true; bool redraw = true; - auto submit_handler = [this, &json_chapters, &tabs, &selected_tab, &loop_running, &redraw]() { + auto window_size_u = window.getSize(); + window_size.x = window_size_u.x; + window_size.y = window_size_u.y; + + auto submit_handler = [this, &after_submit_handler, &json_chapters, &tabs, &selected_tab, &loop_running, &redraw]() { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); if(!selected_item) return; std::vector new_tabs; PluginResult submit_result = tabs[selected_tab].page->submit(selected_item->get_title(), selected_item->url, new_tabs); - if(submit_result == PluginResult::OK) { - if(tabs[selected_tab].page->is_single_page()) { - tabs[selected_tab].search_bar->clear(); - if(new_tabs.size() == 1) - tabs[selected_tab].body = std::move(new_tabs[0].body); - else - loop_running = false; - return; - } + if(submit_result != PluginResult::OK) { + // TODO: Show the exact cause of error (get error message from curl). + show_notification("QuickMedia", std::string("Submit failed for page ") + tabs[selected_tab].page->get_title(), Urgency::CRITICAL); + return; + } - if(new_tabs.empty()) - return; + if(after_submit_handler) + after_submit_handler(); - for(Tab &tab : tabs) { - tab.body->clear_cache(); - } + if(tabs[selected_tab].page->is_single_page()) { + tabs[selected_tab].search_bar->clear(); + if(new_tabs.size() == 1) + tabs[selected_tab].body = std::move(new_tabs[0].body); + else + loop_running = false; + return; + } - if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::MANGA_IMAGES) { - select_episode(selected_item, false); - Body *chapters_body = tabs[selected_tab].body.get(); - chapters_body->filter_search_fuzzy(""); // Needed (or not really) to go to the next chapter when reaching the last page of a chapter - MangaImagesPage *manga_images_page = static_cast(new_tabs[0].page.get()); - window.setKeyRepeatEnabled(false); - while(true) { - if(current_page == PageType::IMAGES) { - window.setFramerateLimit(20); - while(current_page == PageType::IMAGES) { - int page_navigation = image_page(manga_images_page, chapters_body); - if(page_navigation == -1) { - // TODO: Make this work if the list is sorted differently than from newest to oldest. - chapters_body->select_next_item(); - select_episode(chapters_body->get_selected(), true); - image_index = 99999; // Start at the page that shows we are at the end of the chapter - manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); - } else if(page_navigation == 1) { - // TODO: Make this work if the list is sorted differently than from newest to oldest. - chapters_body->select_previous_item(); - select_episode(chapters_body->get_selected(), true); - manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); - } + if(new_tabs.empty()) + return; + + for(Tab &tab : tabs) { + tab.body->clear_cache(); + } + + if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::MANGA_IMAGES) { + select_episode(selected_item, false); + Body *chapters_body = tabs[selected_tab].body.get(); + chapters_body->filter_search_fuzzy(""); // Needed (or not really) to go to the next chapter when reaching the last page of a chapter + MangaImagesPage *manga_images_page = static_cast(new_tabs[0].page.get()); + window.setKeyRepeatEnabled(false); + while(true) { + if(current_page == PageType::IMAGES) { + window.setFramerateLimit(20); + while(current_page == PageType::IMAGES) { + int page_navigation = image_page(manga_images_page, chapters_body); + if(page_navigation == -1) { + // TODO: Make this work if the list is sorted differently than from newest to oldest. + chapters_body->select_next_item(); + select_episode(chapters_body->get_selected(), true); + image_index = 99999; // Start at the page that shows we are at the end of the chapter + manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); + } else if(page_navigation == 1) { + // TODO: Make this work if the list is sorted differently than from newest to oldest. + chapters_body->select_previous_item(); + select_episode(chapters_body->get_selected(), true); + manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url); } - if(vsync_set) - window.setFramerateLimit(0); - else - window.setFramerateLimit(monitor_hz); - } else if(current_page == PageType::IMAGES_CONTINUOUS) { - image_continuous_page(manga_images_page); - } else { - break; } + if(vsync_set) + window.setFramerateLimit(0); + else + window.setFramerateLimit(monitor_hz); + } else if(current_page == PageType::IMAGES_CONTINUOUS) { + image_continuous_page(manga_images_page); + } else { + break; } - window.setKeyRepeatEnabled(true); - } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::IMAGE_BOARD_THREAD) { - current_page = PageType::IMAGE_BOARD_THREAD; - image_board_thread_page(static_cast(new_tabs[0].page.get()), new_tabs[0].body.get()); - } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) { - current_page = PageType::VIDEO_CONTENT; - video_content_page(new_tabs[0].page.get(), selected_item->url, selected_item->get_title(), false); - } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::CHAT) { - current_page = PageType::CHAT; - current_chat_room = matrix->get_room_by_id(selected_item->url); - chat_page(static_cast(new_tabs[0].page.get()), current_chat_room); - select_body_item_by_room(tabs[selected_tab].body.get(), current_chat_room); - current_chat_room = nullptr; - } else { - page_loop(new_tabs); - } - tabs[selected_tab].page->on_navigate_to_page(tabs[selected_tab].body.get()); - if(content_storage_json.isObject()) { - const Json::Value &chapters_json = content_storage_json["chapters"]; - if(chapters_json.isObject()) - json_chapters = &chapters_json; } - redraw = true; + window.setKeyRepeatEnabled(true); + } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::IMAGE_BOARD_THREAD) { + current_page = PageType::IMAGE_BOARD_THREAD; + image_board_thread_page(static_cast(new_tabs[0].page.get()), new_tabs[0].body.get()); + } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) { + current_page = PageType::VIDEO_CONTENT; + video_content_page(static_cast(new_tabs[0].page.get()), selected_item->url, selected_item->get_title(), false); + } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::CHAT) { + current_page = PageType::CHAT; + current_chat_room = matrix->get_room_by_id(selected_item->url); + chat_page(static_cast(new_tabs[0].page.get()), current_chat_room); + select_body_item_by_room(tabs[selected_tab].body.get(), current_chat_room); + current_chat_room = nullptr; } else { - // TODO: Show the exact cause of error (get error message from curl). - show_notification("QuickMedia", std::string("Submit failed for page ") + tabs[selected_tab].page->get_title(), Urgency::CRITICAL); + page_loop(new_tabs); + } + for(Tab &tab : tabs) { + tab.page->on_navigate_to_page(tab.body.get()); } + if(content_storage_json.isObject()) { + const Json::Value &chapters_json = content_storage_json["chapters"]; + if(chapters_json.isObject()) + json_chapters = &chapters_json; + } + redraw = true; }; for(size_t i = 0; i < tabs.size(); ++i) { @@ -1121,7 +1155,12 @@ namespace QuickMedia { hit_bottom = false; break; } - if(hit_bottom && tab_associated_data[selected_tab].fetch_status == FetchStatus::NONE && !tab_associated_data[selected_tab].fetching_next_page_running && tabs[selected_tab].page) { + if(hit_bottom + && tab_associated_data[selected_tab].fetch_status == FetchStatus::NONE + && !tab_associated_data[selected_tab].fetching_next_page_running + && tabs[selected_tab].page + && (!tabs[selected_tab].page->is_lazy_fetch_page() || tab_associated_data[selected_tab].lazy_fetch_finished)) + { gradient_inc = 0.0; tab_associated_data[selected_tab].fetching_next_page_running = true; int next_page = tab_associated_data[selected_tab].fetched_page + 1; @@ -1259,6 +1298,7 @@ namespace QuickMedia { associated_data.lazy_fetch_finished = true; FetchResult fetch_result = associated_data.fetch_future.get(); tabs[i].body->items = std::move(fetch_result.body_items); + tabs[i].body->filter_search_fuzzy(tabs[i].search_bar->get_text()); if(fetch_result.result != PluginResult::OK) associated_data.search_result_text.setString("Failed to fetch page!"); else if(tabs[i].body->items.empty()) @@ -1433,7 +1473,7 @@ namespace QuickMedia { return true; } - void Program::save_recommendations_from_related_videos(const std::string &video_url, const std::string &video_title, const Body *related_media_body) { + void Program::save_recommendations_from_related_videos(const std::string &video_url, const std::string &video_title, const BodyItems &related_media_body_items) { std::string video_id; if(!youtube_url_extract_id(video_url, video_id)) { std::string err_msg = "Failed to extract id of youtube url "; @@ -1465,7 +1505,7 @@ namespace QuickMedia { } int saved_recommendation_count = 0; - for(const auto &body_item : related_media_body->items) { + for(const auto &body_item : related_media_body_items) { std::string recommended_video_id; if(youtube_url_extract_id(body_item->url, recommended_video_id)) { Json::Value &existing_recommendation = recommended_json[recommended_video_id]; @@ -1514,6 +1554,10 @@ namespace QuickMedia { && (memcmp(&result[36], "moov", 4) != 0); } + const char* Program::get_plugin_name() const { + return plugin_name; + } + TaskResult Program::run_task_with_loading_screen(std::function callback) { assert(std::this_thread::get_id() == main_thread_id); @@ -1567,17 +1611,13 @@ namespace QuickMedia { #define CLEANMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask|Mod4Mask|Mod5Mask)) - void Program::video_content_page(Page *page, std::string video_url, std::string video_title, bool download_if_streaming_fails) { + void Program::video_content_page(VideoPage *video_page, std::string video_url, std::string video_title, bool download_if_streaming_fails) { sf::Clock time_watched_timer; bool added_recommendations = false; bool video_loaded = false; PageType previous_page = pop_page_stack(); - sf::Sprite load_sprite(loading_icon); - sf::Vector2u loading_icon_size = loading_icon.getSize(); - load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f); - bool video_url_is_local = false; if(download_if_streaming_fails) { Path video_cache_dir = get_cache_dir().join("video"); @@ -1629,14 +1669,9 @@ namespace QuickMedia { time_watched_timer.restart(); std::unique_ptr video_player; - std::unique_ptr related_media_window; - sf::Vector2f related_media_window_size; - bool related_media_window_visible = false; - sf::Text related_videos_text("Related videos", *FontLoader::get_font(FontLoader::FontType::LATIN_BOLD), 20); - const float related_videos_text_height = related_videos_text.getCharacterSize(); - - auto related_media_body = create_body(); + BodyItems related_videos; + std::string channel_url; sf::WindowHandle video_player_window = None; auto on_window_create = [this, &video_player_window](sf::WindowHandle _video_player_window) mutable { @@ -1645,7 +1680,7 @@ namespace QuickMedia { XSync(disp, False); }; - auto load_video_error_check = [this, &related_media_body, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, &added_recommendations, page]() mutable { + auto load_video_error_check = [this, &related_videos, &channel_url, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, &added_recommendations, video_page]() mutable { time_watched_timer.restart(); video_loaded = false; added_recommendations = false; @@ -1657,9 +1692,9 @@ namespace QuickMedia { show_notification("QuickMedia", err_msg.c_str(), Urgency::CRITICAL); current_page = previous_page; } else { - related_media_body->clear_items(); - related_media_body->clear_thumbnails(); - related_media_body->items = page->get_related_media(video_url); + channel_url.clear(); + // TODO: Make async. What if the server is frozen? + related_videos = video_page->get_related_media(video_url, channel_url); // TODO: Make this also work for other video plugins if(strcmp(plugin_name, "youtube") != 0) @@ -1698,7 +1733,7 @@ namespace QuickMedia { }; bool has_video_started = true; - auto video_event_callback = [this, &related_media_body, &video_url, &video_title, &video_player, &load_video_error_check, previous_page, &has_video_started, &time_watched_timer, &video_loaded](const char *event_name) mutable { + auto video_event_callback = [this, &related_videos, &video_url, &video_title, &video_player, &load_video_error_check, previous_page, &has_video_started, &time_watched_timer, &video_loaded](const char *event_name) mutable { bool end_of_file = false; if(strcmp(event_name, "pause") == 0) { double time_remaining = 9999.0; @@ -1720,7 +1755,7 @@ namespace QuickMedia { std::string new_video_title; // Find video that hasn't been played before in this video session // TODO: Remove duplicates - for(auto it = related_media_body->items.begin(), end = related_media_body->items.end(); it != end; ++it) { + for(auto it = related_videos.begin(), end = related_videos.end(); it != end; ++it) { if(watched_videos.find((*it)->url) == watched_videos.end()) { new_video_url = (*it)->url; new_video_title = (*it)->get_title(); @@ -1776,62 +1811,20 @@ namespace QuickMedia { while (current_page == PageType::VIDEO_CONTENT && window.isOpen()) { while (window.pollEvent(event)) { - base_event_handler(event, previous_page, related_media_body.get(), nullptr, true, false); - if(event.type == sf::Event::Resized && related_media_window) { - related_media_window_size.x = window_size.x * RELATED_MEDIA_WINDOW_WIDTH; - related_media_window_size.y = window_size.y; - related_media_window->setSize(sf::Vector2u(related_media_window_size.x, related_media_window_size.y)); - related_media_window->setPosition(sf::Vector2i(window_size.x - related_media_window_size.x, 0)); - sf::FloatRect visible_area(0, 0, related_media_window_size.x, related_media_window_size.y); - related_media_window->setView(sf::View(visible_area)); - } - } - - while(related_media_window && related_media_window->pollEvent(event)) { - if(!related_media_window_visible) - continue; - if(event.type == sf::Event::KeyPressed) { - if(event.key.code == sf::Keyboard::Up) { - related_media_body->select_previous_item(); - } else if(event.key.code == sf::Keyboard::Down) { - related_media_body->select_next_item(); - } else if(event.key.code == sf::Keyboard::PageUp) { - related_media_body->select_previous_page(); - } else if(event.key.code == sf::Keyboard::PageDown) { - related_media_body->select_next_page(); - } else if(event.key.code == sf::Keyboard::Home) { - related_media_body->select_first_item(); - } else if(event.key.code == sf::Keyboard::End) { - related_media_body->select_last_item(); - } else if(event.key.code == sf::Keyboard::Escape) { - related_media_window_visible = false; - related_media_window->setVisible(false); - } else if(event.key.code == sf::Keyboard::R && event.key.control) { - related_media_window_visible = false; - related_media_window->setVisible(related_media_window_visible); - related_media_body->clear_cache(); - } else if(event.key.code == sf::Keyboard::F && event.key.control) { - window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE); - fullscreen = !fullscreen; - } else if(event.key.code == sf::Keyboard::Enter) { - BodyItem *selected_item = related_media_body->get_selected(); - if(!selected_item) - continue; - - related_media_window_visible = false; - related_media_window->setVisible(false); - has_video_started = false; - - video_url = selected_item->url; - video_title = selected_item->get_title(); - load_video_error_check(); - } else if(event.key.code == sf::Keyboard::C && event.key.control) { - save_video_url_to_clipboard(); - } + if (event.type == sf::Event::Closed) { + current_page = PageType::EXIT; + window.close(); + } else if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) { + current_page = previous_page; } } - if(video_player_window && XCheckTypedWindowEvent(disp, video_player_window, KeyPress, &xev)/* && xev.xkey.subwindow == video_player_window*/ && !related_media_window_visible) { + if(video_player_window && XCheckTypedWindowEvent(disp, video_player_window, KeyPress, &xev)/* && xev.xkey.subwindow == video_player_window*/) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" KeySym pressed_keysym = XKeycodeToKeysym(disp, xev.xkey.keycode, 0); @@ -1842,26 +1835,47 @@ namespace QuickMedia { } else if(pressed_keysym == XK_f && pressing_ctrl) { window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE); fullscreen = !fullscreen; - } else if(pressed_keysym == XK_r && pressing_ctrl && strcmp(plugin_name, "4chan") != 0) { - if(!related_media_window) { - related_media_window_size.x = window_size.x * RELATED_MEDIA_WINDOW_WIDTH; - related_media_window_size.y = window_size.y; - related_media_window = std::make_unique(sf::VideoMode(related_media_window_size.x, related_media_window_size.y), "", 0); - related_media_window->setFramerateLimit(0); - if(!enable_vsync(disp, related_media_window->getSystemHandle())) { - fprintf(stderr, "Failed to enable vsync, fallback to frame limiting\n"); - related_media_window->setFramerateLimit(monitor_hz); - } - related_media_window->setVisible(false); - XReparentWindow(disp, related_media_window->getSystemHandle(), video_player_window, window_size.x - related_media_window_size.x, 0); - XSync(disp, False); - } - - related_media_window_visible = true; - related_media_window->setVisible(related_media_window_visible); + } else if(pressed_keysym == XK_r && pressing_ctrl) { if(!cursor_visible) window.setMouseCursorVisible(true); cursor_visible = true; + + int search_delay = 0; + auto search_page = video_page->create_search_page(this, search_delay); + auto related_videos_page = video_page->create_related_videos_page(this, video_url, video_title); + auto channels_page = video_page->create_channels_page(this, channel_url); + if(search_page || related_videos_page || channels_page) { + XUnmapWindow(disp, video_player_window); + XSync(disp, False); + + std::vector tabs; + if(search_page) { + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", search_delay)}); + } + if(related_videos_page) { + auto related_videos_body = create_body(); + related_videos_body->items = related_videos; + tabs.push_back(Tab{std::move(related_videos_body), std::move(related_videos_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + } + if(channels_page) { + tabs.push_back(Tab{create_body(), std::move(channels_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + } + + bool page_changed = false; + page_loop(tabs, 1, [this, &video_player, &page_changed]() { + window.setMouseCursorVisible(true); + video_player.reset(); + page_changed = true; + }); + + if(page_changed) { + current_page = previous_page; + return; + } else { + XMapWindow(disp, video_player_window); + XSync(disp, False); + } + } } else if(pressed_keysym == XK_c && pressing_ctrl) { save_video_url_to_clipboard(); } @@ -1893,7 +1907,7 @@ namespace QuickMedia { if(!video_loaded) { window.clear(back_color); load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f); - load_sprite.setRotation(time_watched_timer.getElapsedTime().asSeconds() * 400.0); + load_sprite.setRotation(load_sprite_timer.getElapsedTime().asSeconds() * 400.0); window.draw(load_sprite); window.display(); continue; @@ -1902,22 +1916,11 @@ namespace QuickMedia { /* Only save recommendations for the video if we have been watching it for 15 seconds */ if(is_youtube && video_loaded && !added_recommendations && time_watched_timer.getElapsedTime().asSeconds() >= 15) { added_recommendations = true; - save_recommendations_from_related_videos(video_url, video_title, related_media_body.get()); + save_recommendations_from_related_videos(video_url, video_title, related_videos); } +#if 0 if(video_player_window) { - if(related_media_window && related_media_window_visible) { - sf::Vector2f body_pos, body_size; - get_related_media_body_dimensions(window_size, body_pos, body_size, related_videos_text_height); - - related_media_window->clear(back_color); - related_videos_text.setPosition(body_pos.x, 10.0f); - related_media_window->draw(related_videos_text); - related_media_body->draw(*related_media_window, body_pos, body_size); - related_media_window->display(); - continue; - } - if(!cursor_visible) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); continue; @@ -1930,6 +1933,9 @@ namespace QuickMedia { } } std::this_thread::sleep_for(std::chrono::milliseconds(50)); + #endif + window.clear(back_color); + window.display(); } window.setMouseCursorVisible(true); diff --git a/src/plugins/ImageBoard.cpp b/src/plugins/ImageBoard.cpp index ac05f80..6f97082 100644 --- a/src/plugins/ImageBoard.cpp +++ b/src/plugins/ImageBoard.cpp @@ -1,7 +1,7 @@ #include "../../plugins/ImageBoard.hpp" namespace QuickMedia { - BodyItems ImageBoardThreadPage::get_related_media(const std::string &url) { + BodyItems ImageBoardThreadPage::get_related_media(const std::string &url, std::string&) { BodyItems body_items; auto it = std::find(cached_media_urls.begin(), cached_media_urls.end(), url); if(it == cached_media_urls.end()) @@ -16,6 +16,10 @@ namespace QuickMedia { return body_items; } + std::unique_ptr ImageBoardThreadPage::create_related_videos_page(Program*, const std::string&, const std::string&) { + return nullptr; + } + PluginResult ImageBoardThreadPage::login(const std::string &token, const std::string &pin, std::string &response_msg) { (void)token; (void)pin; diff --git a/src/plugins/Page.cpp b/src/plugins/Page.cpp index e444ecd..9e0e97a 100644 --- a/src/plugins/Page.cpp +++ b/src/plugins/Page.cpp @@ -3,11 +3,6 @@ #include namespace QuickMedia { - BodyItems Page::get_related_media(const std::string &url) { - (void)url; - return {}; - } - DownloadResult Page::download_json(Json::Value &result, const std::string &url, std::vector additional_args, bool use_browser_useragent, std::string *err_msg) { std::string server_response; if(download_to_string(url, server_response, std::move(additional_args), is_tor_enabled(), use_browser_useragent, err_msg == nullptr) != DownloadResult::OK) { diff --git a/src/plugins/Pornhub.cpp b/src/plugins/Pornhub.cpp index f527e76..1f0840e 100644 --- a/src/plugins/Pornhub.cpp +++ b/src/plugins/Pornhub.cpp @@ -138,16 +138,28 @@ namespace QuickMedia { return search_result_to_plugin_result(get_videos_in_page(url, is_tor_enabled(), result_items)); } - PluginResult PornhubSearchPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { - (void)title; - (void)url; + PluginResult PornhubSearchPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); return PluginResult::OK; } - BodyItems PornhubVideoPage::get_related_media(const std::string &url) { + PluginResult PornhubRelatedVideosPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + return PluginResult::OK; + } + + BodyItems PornhubVideoPage::get_related_media(const std::string &url, std::string&) { BodyItems result_items; get_videos_in_page(url, is_tor_enabled(), result_items); return result_items; } + + std::unique_ptr PornhubVideoPage::create_search_page(Program *program, int &search_delay) { + search_delay = 500; + return std::make_unique(program); + } + + std::unique_ptr PornhubVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) { + return std::make_unique(program); + } } \ No newline at end of file diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 1ca25a3..2418073 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -647,9 +647,33 @@ namespace QuickMedia { return PluginResult::OK; } + PluginResult YoutubeChannelPage::lazy_fetch(BodyItems &result_items) { + std::vector additional_args = { + { "-H", "x-spf-referer: " + url }, + { "-H", "x-youtube-client-name: 1" }, + { "-H", "x-spf-previous: " + url }, + { "-H", "x-youtube-client-version: 2.20200626.03.00" }, + { "-H", "referer: " + url } + }; + + //std::vector cookies = get_cookies(); + //additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); + + Json::Value json_root; + DownloadResult result = download_json(json_root, url + "/videos?pbj=1", std::move(additional_args), true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + result_items = parse_channel_videos(json_root, continuation_token, added_videos); + return PluginResult::OK; + } + + PluginResult YoutubeRelatedVideosPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + return PluginResult::OK; + } + // TODO: Make this faster by using string search instead of parsing html. // TODO: If the result is a play - BodyItems YoutubeVideoPage::get_related_media(const std::string &url) { + BodyItems YoutubeVideoPage::get_related_media(const std::string &url, std::string &channel_url) { BodyItems result_items; std::string modified_url = remove_index_from_playlist_url(url); @@ -677,6 +701,18 @@ namespace QuickMedia { if(!json_item.isObject()) continue; + if(channel_url.empty()) { + const Json::Value &player_response_json = json_item["playerResponse"]; + if(player_response_json.isObject()) { + const Json::Value &video_details_json = player_response_json["videoDetails"]; + if(video_details_json.isObject()) { + const Json::Value &channel_id_json = video_details_json["channelId"]; + if(channel_id_json.isString()) + channel_url = "https://www.youtube.com/channel/" + channel_id_json.asString(); + } + } + } + const Json::Value &response_json = json_item["response"]; if(!response_json.isObject()) continue; @@ -730,4 +766,17 @@ namespace QuickMedia { return result_items; } + + std::unique_ptr YoutubeVideoPage::create_search_page(Program *program, int &search_delay) { + search_delay = 350; + return std::make_unique(program); + } + + std::unique_ptr YoutubeVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) { + return std::make_unique(program); + } + + std::unique_ptr YoutubeVideoPage::create_channels_page(Program *program, const std::string &channel_url) { + return std::make_unique(program, channel_url, "", "Channel videos"); + } } \ No newline at end of file -- cgit v1.2.3