#include "../../plugins/Saucenao.hpp" #include "../../plugins/Info.hpp" #include "../../include/StringUtils.hpp" #include "../../include/Theme.hpp" #include namespace QuickMedia { class SaucenaoBodyItemUrls : public BodyItemExtra { public: std::vector urls; }; static int string_views_equal_case_insensitive(const QuickMediaStringView str1, const QuickMediaStringView str2) { if(str2.size != str1.size) return 1; for(size_t i = 0; i < str1.size; ++i) { char c1 = str1.data[i]; char c2 = str2.data[i]; if(to_upper(c1) != to_upper(c2)) return 1; } return 0; } static QuickMediaHtmlAttribute* get_attribute_by_name(QuickMediaHtmlNode *node, QuickMediaStringView name) { for(QuickMediaHtmlAttribute *attr = node->first_attribute; attr; attr = attr->next) { if(string_views_equal_case_insensitive(attr->key, name) == 0) return attr; } return NULL; } static void match_node_get_urls(QuickMediaHtmlNode *node, std::vector &urls) { if(node->is_tag && string_views_equal_case_insensitive(node->name, {"a", 1}) == 0) { QuickMediaHtmlAttribute *attr = get_attribute_by_name(node, {"href", 4}); // Ignore saucenao info pages if(attr && !memmem(attr->value.data, attr->value.size, "saucenao.com", 12)) urls.emplace_back(attr->value.data, attr->value.size); } QuickMediaHtmlChildNode *child_node = node->first_child; while(child_node) { match_node_get_urls(&child_node->node, urls); child_node = child_node->next; } } PluginResult SaucenaoPage::submit(const SubmitArgs &args, std::vector &result_tabs) { if(!args.extra) return PluginResult::OK; SaucenaoBodyItemUrls *urls = static_cast(args.extra.get()); BodyItems body_items; for(const std::string &url : urls->urls) { body_items.push_back(InfoPage::add_url(url)); } body_items.push_back(InfoPage::add_google_search(args.title)); auto body = create_body(); body->set_items(std::move(body_items)); result_tabs.push_back(Tab{ std::move(body), std::make_unique(program), create_search_bar("Search...", SEARCH_DELAY_FILTER) }); return PluginResult::OK; } PluginResult SaucenaoPage::lazy_fetch(BodyItems &result_items) { std::vector additional_args; if(is_local) { additional_args.push_back({ "-F", "file=@" + path }); } else { additional_args.push_back({ "--form-string", "url=" + path }); } std::string website_data; DownloadResult download_result = download_to_string("https://saucenao.com/search.php", website_data, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); QuickMediaHtmlSearch html_search; int result = quickmedia_html_search_init(&html_search, website_data.c_str(), website_data.size()); if(result != 0) goto cleanup; result = quickmedia_html_find_nodes_xpath(&html_search, "//td[class='resulttablecontent']", [](QuickMediaMatchNode *node, void *userdata) { BodyItems *item_data = (BodyItems*)userdata; QuickMediaStringView text = quickmedia_html_node_get_text(node); if(text.data) { std::string title(text.data, text.size); size_t p_index = title.find("%"); if(p_index != std::string::npos) title = title.erase(0, p_index + 1); title = strip(title); std::string description; const size_t title_line_end = title.find('\n'); if(title_line_end != std::string::npos) { description = title.substr(title_line_end + 1); title.erase(title_line_end); } auto urls = std::make_shared(); match_node_get_urls(node->node, urls->urls); if(!urls->urls.empty()) { if(!description.empty()) description += "\n\n"; int index = 0; for(const std::string &url : urls->urls) { if(index > 0) description += '\n'; description += url; ++index; } } auto item = BodyItem::create(std::move(title)); item->set_description(std::move(description)); item->set_description_color(get_theme().faded_text_color); item->extra = std::move(urls); item_data->push_back(std::move(item)); } return 0; }, &result_items); BodyItemContext body_item_context; body_item_context.body_items = &result_items; body_item_context.index = 0; quickmedia_html_find_nodes_xpath(&html_search, "//td[class='resulttableimage']//img", [](QuickMediaMatchNode *node, void *userdata) { BodyItemContext *item_data = (BodyItemContext*)userdata; QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node, "src"); QuickMediaStringView data_src = quickmedia_html_node_get_attribute_value(node, "data-src"); QuickMediaStringView image_url = data_src.data ? data_src : src; if(image_url.data && item_data->index < item_data->body_items->size()) { (*item_data->body_items)[item_data->index]->thumbnail_url.assign(image_url.data, image_url.size); (*item_data->body_items)[item_data->index]->thumbnail_size = mgl::vec2i(150, 147); item_data->index++; } return 0; }, &body_item_context); cleanup: quickmedia_html_search_deinit(&html_search); if(result != 0) { result_items.clear(); return PluginResult::ERR; } return PluginResult::OK; } }