aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-09-12 15:33:49 +0200
committerdec05eba <dec05eba@protonmail.com>2021-09-12 15:33:49 +0200
commita9d69f57a0cd4f9cccff07b2890a860695d1e7ed (patch)
treef03e747dc6a0f52fbbef3772437ddf230a05c721
parentde8682ce581cdc3f0886e3edf89b430a7215aea1 (diff)
Mangadex: add author/artist tabs for manga, optimize search (covers)
-rw-r--r--TODO2
-rw-r--r--plugins/Mangadex.hpp26
-rw-r--r--plugins/Page.hpp4
-rw-r--r--src/plugins/Mangadex.cpp235
-rw-r--r--src/plugins/Soundcloud.cpp4
5 files changed, 152 insertions, 119 deletions
diff --git a/TODO b/TODO
index 4ce55f7..38b4f31 100644
--- a/TODO
+++ b/TODO
@@ -185,7 +185,7 @@ Youtube is still getting throttled, but to 500kb/sec from 5mb/sec. How to detect
Opening a media url should display it directly in quickmedia.
Automatically resize body item thumbnail if body is small, or move thumbnail above the text.
Support &nbsp?.
-Add option to navigate studios/producers/author in AniList/MAL.
+Add option to navigate studios/producers/author in AniList.
Renable throttle detection after fixing it (it doesn't detect throttling well and it breaks for very long videos, such as 8 hour long videos).
Show who deleted a message in matrix.
Sync should replace all messages in the room (except for the selected room?) to reduce ram usage when in many rooms and when quickmedia has been running for a long time doing sync.
diff --git a/plugins/Mangadex.hpp b/plugins/Mangadex.hpp
index beceddb..67f68b7 100644
--- a/plugins/Mangadex.hpp
+++ b/plugins/Mangadex.hpp
@@ -17,19 +17,15 @@ namespace QuickMedia {
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<Tab> &result_tabs) override;
const char* get_bookmark_name() const override { return "mangadex"; }
-
- ChapterImageUrls chapter_image_urls;
private:
- // Cover id should be set in the body items thumbnail url
- PluginResult get_cover_urls(BodyItems &body_items);
- SearchResult search(const std::string &str, int page, BodyItems &result_items);
bool get_rememberme_token(std::string &rememberme_token);
std::optional<std::string> rememberme_token;
};
class MangadexChaptersPage : public MangaChaptersPage {
public:
- MangadexChaptersPage(Program *program, MangadexSearchPage *search_page, std::string manga_name, std::string manga_url) : MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)), search_page(search_page) {}
+ MangadexChaptersPage(Program *program, MangadexSearchPage *search_page, std::string manga_name, std::string manga_url, ChapterImageUrls chapter_image_urls) :
+ MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)), search_page(search_page), chapter_image_urls(std::move(chapter_image_urls)) {}
PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
const char* get_bookmark_name() const override { return "mangadex"; }
@@ -38,12 +34,25 @@ namespace QuickMedia {
const char* get_service_name() const override { return "mangadex"; }
private:
MangadexSearchPage *search_page;
+ ChapterImageUrls chapter_image_urls;
+ };
+
+ class MangadexCreatorPage : public LazyFetchPage {
+ public:
+ MangadexCreatorPage(Program *program, MangadexSearchPage *search_page, Creator creator) : LazyFetchPage(program), search_page(search_page), creator(std::move(creator)) {}
+ const char* get_title() const override { return creator.name.c_str(); }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
+ PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
+ PluginResult lazy_fetch(BodyItems &result_items) override;
+ private:
+ MangadexSearchPage *search_page;
+ Creator creator;
};
class MangadexImagesPage : public MangaImagesPage {
public:
- MangadexImagesPage(Program *program, MangadexSearchPage *search_page, std::string manga_name, std::string chapter_id, std::string chapter_name) :
- MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(chapter_id)), search_page(search_page) {}
+ MangadexImagesPage(Program *program, MangadexSearchPage *search_page, std::string manga_name, std::string chapter_id, std::string chapter_name, ChapterImageUrls *chapter_image_url_templates) :
+ MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(chapter_id)), search_page(search_page), chapter_image_url_templates(chapter_image_url_templates) {}
ImageResult get_number_of_images(int &num_images) override;
ImageResult for_each_page_in_chapter(PageCallback callback) override;
const char* get_service_name() const override { return "mangadex"; }
@@ -52,5 +61,6 @@ namespace QuickMedia {
ImageResult get_image_urls_for_chapter(const std::string &url);
private:
MangadexSearchPage *search_page;
+ ChapterImageUrls *chapter_image_url_templates;
};
} \ No newline at end of file
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index 29fce27..d5daa3d 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -11,7 +11,7 @@ namespace QuickMedia {
class Program;
constexpr int SEARCH_DELAY_FILTER = 50;
- // TODO: Remove to PageType when the other PageType is removed
+ // TODO: Rename to PageType when the other PageType is removed
enum class PageTypez {
REGULAR,
MANGA_IMAGES,
@@ -50,7 +50,7 @@ namespace QuickMedia {
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 fetched is 1 (search page is 0)
+ // Note: the first page fetched is 1 (search/lazy fetch 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; }
DownloadResult download_json(Json::Value &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr);
diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp
index 34bcfc7..9209828 100644
--- a/src/plugins/Mangadex.cpp
+++ b/src/plugins/Mangadex.cpp
@@ -59,91 +59,8 @@ namespace QuickMedia {
return PluginResult::OK;
}
-
- static std::shared_ptr<BodyItem> relationship_get_body_item(const Json::Value &json, BodyItems &body_items) {
- for(const Json::Value &item_json : json) {
- if(!item_json.isObject())
- continue;
-
- const Json::Value &type_json = item_json["type"];
- if(!type_json.isString() || strcmp(type_json.asCString(), "manga") != 0)
- continue;
-
- const Json::Value &id_json = item_json["id"];
- if(!id_json.isString())
- continue;
-
- std::string id_str = id_json.asString();
- auto it = std::find_if(body_items.begin(), body_items.end(), [&id_str](const std::shared_ptr<BodyItem> &body_item) {
- return body_item->url == id_str;
- });
-
- if(it == body_items.end())
- continue;
-
- return *it;
- }
- return nullptr;
- }
-
- PluginResult MangadexSearchPage::get_cover_urls(BodyItems &body_items) {
- if(body_items.empty())
- return PluginResult::OK;
-
- std::string url = "https://api.mangadex.org/cover?limit=100&order[updatedAt]=desc";
- for(size_t i = 0; i < body_items.size(); ++i) {
- if(!body_items[i]->thumbnail_url.empty())
- url += "&ids[]=" + body_items[i]->thumbnail_url;
- }
-
- Json::Value json_root;
- if(download_json(json_root, url, {}, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
-
- if(!json_root.isObject())
- return PluginResult::OK;
-
- const Json::Value &results_json = json_root["results"];
- if(!results_json.isArray())
- return PluginResult::OK;
-
- std::shared_ptr<BodyItem> body_item;
- for(const Json::Value &result_item_json : results_json) {
- if(!result_item_json.isObject())
- continue;
-
- const Json::Value &result_json = result_item_json["result"];
- if(!result_json.isString() || strcmp(result_json.asCString(), "ok") != 0)
- continue;
-
- const Json::Value &relationships_json = result_item_json["relationships"];
- if(!relationships_json.isArray())
- continue;
-
- body_item = relationship_get_body_item(relationships_json, body_items);
- if(!body_item)
- continue;
-
- const Json::Value &data_json = result_item_json["data"];
- if(!data_json.isObject())
- continue;
-
- const Json::Value &attributes_json = data_json["attributes"];
- if(!attributes_json.isObject())
- continue;
-
- const Json::Value &filename_json = attributes_json["fileName"];
- if(!filename_json.isString())
- continue;
-
- body_item->thumbnail_url = "https://uploads.mangadex.org/covers/" + body_item->url + "/" + filename_json.asString() + ".256.jpg";
- body_item->thumbnail_size = {101, 141};
- }
-
- return PluginResult::OK;
- }
- static std::string relationships_get_cover_art(const Json::Value &relationships_json) {
+ static std::string relationships_get_cover_art_filename(const Json::Value &relationships_json) {
std::string result;
if(!relationships_json.isArray())
return result;
@@ -152,31 +69,53 @@ namespace QuickMedia {
if(!relationship_json.isObject())
continue;
- const Json::Value &id_json = relationship_json["id"];
const Json::Value &relationship_type_json = relationship_json["type"];
- if(!id_json.isString() || !relationship_type_json.isString() || strcmp(relationship_type_json.asCString(), "cover_art") != 0)
+ if(!relationship_type_json.isString() || strcmp(relationship_type_json.asCString(), "cover_art") != 0)
+ continue;
+
+ const Json::Value &attributes_json = relationship_json["attributes"];
+ if(!attributes_json.isObject())
continue;
- result = id_json.asString();
+ const Json::Value &file_name_json = attributes_json["fileName"];
+ if(!file_name_json.isString())
+ continue;
+
+ result = file_name_json.asString();
break;
}
return result;
}
- SearchResult MangadexSearchPage::search(const std::string &str, int page, BodyItems &result_items) {
- std::string url = "https://api.mangadex.org/manga?title=" + url_param_encode(str) + "&limit=20&offset=" + std::to_string(page * 20);
+ enum class SearchType {
+ TITLE,
+ AUTHOR
+ };
+
+ static PluginResult search_manga(Page *plugin_page, SearchType search_type, const std::string &query, int page, BodyItems &result_items) {
+ std::string url = "https://api.mangadex.org/manga?limit=20&includes[]=cover_art&offset=" + std::to_string(page * 20);
+ const std::string query_encoded = url_param_encode(query);
+ switch(search_type) {
+ case SearchType::TITLE:
+ url += "&title=" + query_encoded;
+ break;
+ case SearchType::AUTHOR:
+ url += "&authors[]=" + query_encoded;
+ url += "&artists[]=" + query_encoded;
+ break;
+ }
Json::Value json_root;
- if(download_json(json_root, url, {}, true) != DownloadResult::OK)
- return SearchResult::NET_ERR;
+ if(plugin_page->download_json(json_root, url, {}, true) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
if(!json_root.isObject())
- return SearchResult::OK;
+ return PluginResult::OK;
const Json::Value &results_json = json_root["results"];
if(!results_json.isArray())
- return SearchResult::OK;
+ return PluginResult::OK;
for(const Json::Value &result_item_json : results_json) {
if(!result_item_json.isObject())
@@ -220,22 +159,22 @@ namespace QuickMedia {
}
}
- body_item->thumbnail_url = relationships_get_cover_art(result_item_json["relationships"]);
+ std::string cover_art_filename = relationships_get_cover_art_filename(data_json["relationships"]);
+ if(!cover_art_filename.empty())
+ body_item->thumbnail_url = "https://uploads.mangadex.org/covers/" + body_item->url + "/" + std::move(cover_art_filename) + ".256.jpg";
+ body_item->thumbnail_size = {101, 141};
result_items.push_back(std::move(body_item));
}
- // Intentionally ignore errors. This api shouldn't fail if we fail to get covers
- get_cover_urls(result_items);
-
- return SearchResult::OK;
+ return PluginResult::OK;
}
SearchResult MangadexSearchPage::search(const std::string &str, BodyItems &result_items) {
- return search(str, 0, result_items);
+ return plugin_result_to_search_result(search_manga(this, SearchType::TITLE, str, 0, result_items));
}
PluginResult MangadexSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
- return search_result_to_plugin_result(search(str, page, result_items));
+ return search_manga(this, SearchType::TITLE, str, page, result_items);
}
static PluginResult get_chapters_for_manga(Page *page, const std::string &manga_id, int page_num, BodyItems &result_items, ChapterImageUrls &chapter_image_urls) {
@@ -327,23 +266,103 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ static void get_creators(const Json::Value &relationships_json, std::vector<Creator> &creators) {
+ if(!relationships_json.isArray())
+ return;
+
+ for(const Json::Value &relationship_json : relationships_json) {
+ if(!relationship_json.isObject())
+ continue;
+
+ const Json::Value &id_json = relationship_json["id"];
+ if(!id_json.isString())
+ continue;
+
+ const Json::Value &type_json = relationship_json["type"];
+ if(!type_json.isString() || (strcmp(type_json.asCString(), "author") != 0 && strcmp(type_json.asCString(), "artist") != 0))
+ continue;
+
+ const Json::Value &attributes_json = relationship_json["attributes"];
+ if(!attributes_json.isObject())
+ continue;
+
+ const Json::Value &name_json = attributes_json["name"];
+ if(!name_json.isString())
+ continue;
+
+ Creator creator;
+ creator.name = name_json.asString();
+ creator.url = id_json.asString();
+ creators.push_back(std::move(creator));
+ }
+ }
+
+ static PluginResult get_creators_for_manga(Page *page, const std::string &manga_id, std::vector<Creator> &creators) {
+ std::string request_url = "https://api.mangadex.org/manga/" + manga_id + "?includes[]=artist&includes[]=author";
+
+ Json::Value json_root;
+ if(page->download_json(json_root, request_url, {}, true) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
+
+ if(!json_root.isObject())
+ return PluginResult::OK;
+
+ const Json::Value &result_json = json_root["result"];
+ if(!result_json.isString() || strcmp(result_json.asCString(), "ok") != 0)
+ return PluginResult::ERR;
+
+ const Json::Value &data_json = json_root["data"];
+ if(!data_json.isObject())
+ return PluginResult::ERR;
+
+ get_creators(data_json["relationships"], creators);
+ return PluginResult::OK;
+ }
+
+ // TODO: Do requests in parallel
PluginResult MangadexSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
- chapter_image_urls.clear();
- auto body = create_body();
+ PluginResult result;
+
BodyItems body_items;
- get_chapters_for_manga(this, url, 0, body_items, chapter_image_urls);
+ ChapterImageUrls chapter_image_urls;
+ result = get_chapters_for_manga(this, url, 0, body_items, chapter_image_urls);
+ if(result != PluginResult::OK)
+ return result;
+ auto body = create_body();
body->set_items(std::move(body_items));
- result_tabs.push_back(Tab{std::move(body), std::make_unique<MangadexChaptersPage>(program, this, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<MangadexChaptersPage>(program, this, title, url, std::move(chapter_image_urls)), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ std::vector<Creator> creators;
+ result = get_creators_for_manga(this, url, creators);
+ if(result != PluginResult::OK)
+ return result;
+
+ for(Creator &creator : creators) {
+ result_tabs.push_back(Tab{create_body(), std::make_unique<MangadexCreatorPage>(program, this, std::move(creator)), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ }
+
return PluginResult::OK;
}
PluginResult MangadexChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
- result_tabs.push_back(Tab{nullptr, std::make_unique<MangadexImagesPage>(program, search_page, content_title, url, title), nullptr});
+ result_tabs.push_back(Tab{nullptr, std::make_unique<MangadexImagesPage>(program, search_page, content_title, url, title, &chapter_image_urls), nullptr});
return PluginResult::OK;
}
PluginResult MangadexChaptersPage::get_page(const std::string&, int page, BodyItems &result_items) {
- return get_chapters_for_manga(this, content_url, page, result_items, search_page->chapter_image_urls);
+ return get_chapters_for_manga(this, content_url, page, result_items, chapter_image_urls);
+ }
+
+ PluginResult MangadexCreatorPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ return search_page->submit(title, url, result_tabs);
+ }
+
+ PluginResult MangadexCreatorPage::get_page(const std::string&, int page, BodyItems &result_items) {
+ return search_manga(this, SearchType::AUTHOR, creator.url, page, result_items);
+ }
+
+ PluginResult MangadexCreatorPage::lazy_fetch(BodyItems &result_items) {
+ return search_manga(this, SearchType::AUTHOR, creator.url, 0, result_items);
}
bool MangadexChaptersPage::extract_id_from_url(const std::string &url, std::string &manga_id) const {
@@ -401,8 +420,8 @@ namespace QuickMedia {
if(!base_url.empty() && base_url.back() != '/')
base_url += '/';
- auto it = search_page->chapter_image_urls.find(url);
- if(it == search_page->chapter_image_urls.end())
+ auto it = chapter_image_url_templates->find(url);
+ if(it == chapter_image_url_templates->end())
return ImageResult::ERR;
chapter_image_urls.resize(it->second.size());
diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp
index a9b192e..3a9a6e1 100644
--- a/src/plugins/Soundcloud.cpp
+++ b/src/plugins/Soundcloud.cpp
@@ -291,9 +291,13 @@ namespace QuickMedia {
SearchResult SoundcloudSearchPage::search(const std::string &str, BodyItems &result_items) {
query_urn.clear();
+ if(str.empty())
+ return SearchResult::OK;
+
PluginResult result = get_page(str, 0, result_items);
if(result != PluginResult::OK)
return SearchResult::ERR;
+
return SearchResult::OK;
}