aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Dmenu.cpp23
-rw-r--r--src/plugins/FileManager.cpp75
-rw-r--r--src/plugins/Fourchan.cpp649
-rw-r--r--src/plugins/ImageBoard.cpp30
-rw-r--r--src/plugins/Manga.cpp9
-rw-r--r--src/plugins/Mangadex.cpp201
-rw-r--r--src/plugins/Manganelo.cpp277
-rw-r--r--src/plugins/Mangatown.cpp212
-rw-r--r--src/plugins/Matrix.cpp41
-rw-r--r--src/plugins/NyaaSi.cpp131
-rw-r--r--src/plugins/Page.cpp50
-rw-r--r--src/plugins/Plugin.cpp52
-rw-r--r--src/plugins/Pornhub.cpp23
-rw-r--r--src/plugins/Youtube.cpp317
14 files changed, 951 insertions, 1139 deletions
diff --git a/src/plugins/Dmenu.cpp b/src/plugins/Dmenu.cpp
deleted file mode 100644
index 9a8b5b8..0000000
--- a/src/plugins/Dmenu.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include "../../plugins/Dmenu.hpp"
-#include <iostream>
-
-namespace QuickMedia {
- Dmenu::Dmenu() : Plugin("dmenu") {
- std::string line;
- while(std::getline(std::cin, line)) {
- stdin_data.push_back(std::move(line));
- }
- }
-
- PluginResult Dmenu::get_front_page(BodyItems &result_items) {
- for(const std::string &line_data : stdin_data) {
- result_items.push_back(BodyItem::create(line_data));
- }
- return PluginResult::OK;
- }
-
- SearchResult Dmenu::search(const std::string &text, BodyItems&) {
- std::cout << text << std::endl;
- return SearchResult::OK;
- }
-} \ No newline at end of file
diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp
index ccaf2c1..5fac79c 100644
--- a/src/plugins/FileManager.cpp
+++ b/src/plugins/FileManager.cpp
@@ -1,12 +1,8 @@
#include "../../plugins/FileManager.hpp"
#include "../../include/ImageUtils.hpp"
-#include <filesystem>
+#include "../../include/QuickMedia.hpp"
namespace QuickMedia {
- FileManager::FileManager() : Plugin("file-manager"), current_dir("/") {
-
- }
-
// Returns empty string if no extension
static const char* get_ext(const std::filesystem::path &path) {
const char *path_c = path.c_str();
@@ -26,7 +22,45 @@ namespace QuickMedia {
}
}
- PluginResult FileManager::get_files_in_directory(BodyItems &result_items) {
+ PluginResult FileManagerPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)url;
+
+ std::filesystem::path new_path;
+ if(title == "..")
+ new_path = current_dir.parent_path();
+ else
+ new_path = current_dir / title;
+
+ if(std::filesystem::is_regular_file(new_path)) {
+ program->select_file(new_path);
+ return PluginResult::OK;
+ }
+
+ if(!std::filesystem::is_directory(new_path))
+ return PluginResult::ERR;
+
+ current_dir = std::move(new_path);
+
+ BodyItems result_items;
+ PluginResult result = get_files_in_directory(result_items);
+ if(result != PluginResult::OK)
+ return result;
+
+ auto body = create_body();
+ body->items = std::move(result_items);
+ body->draw_thumbnails = true;
+ result_tabs.push_back(Tab{std::move(body), nullptr, nullptr});
+ return PluginResult::OK;
+ }
+
+ bool FileManagerPage::set_current_directory(const std::string &path) {
+ if(!std::filesystem::is_directory(path))
+ return false;
+ current_dir = path;
+ return true;
+ }
+
+ PluginResult FileManagerPage::get_files_in_directory(BodyItems &result_items) {
std::vector<std::filesystem::directory_entry> paths;
try {
for(auto &p : std::filesystem::directory_iterator(current_dir)) {
@@ -58,33 +92,4 @@ namespace QuickMedia {
return PluginResult::OK;
}
-
- bool FileManager::set_current_directory(const std::string &path) {
- if(!std::filesystem::is_directory(path))
- return false;
- current_dir = path;
- return true;
- }
-
- bool FileManager::set_child_directory(const std::string &filename) {
- if(filename == "..") {
- std::filesystem::path new_path = current_dir.parent_path();
- if(std::filesystem::is_directory(new_path)) {
- current_dir = std::move(new_path);
- return true;
- }
- return false;
- } else {
- std::filesystem::path new_path = current_dir / filename;
- if(std::filesystem::is_directory(new_path)) {
- current_dir = std::move(new_path);
- return true;
- }
- return false;
- }
- }
-
- const std::filesystem::path& FileManager::get_current_dir() const {
- return current_dir;
- }
} \ No newline at end of file
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index 1cecc2b..1d3681a 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -1,6 +1,7 @@
#include "../../plugins/Fourchan.hpp"
#include "../../include/DataView.hpp"
#include "../../include/Storage.hpp"
+#include "../../include/StringUtils.hpp"
#include <json/reader.h>
#include <string.h>
#include <tidy.h>
@@ -11,40 +12,9 @@
static const std::string fourchan_url = "https://a.4cdn.org/";
static const std::string fourchan_image_url = "https://i.4cdn.org/";
-namespace QuickMedia {
- Fourchan::Fourchan(const std::string &resources_root) : ImageBoard("4chan"), resources_root(resources_root) {
- thread_list_update_thread = std::thread([this]() {
- BodyItems new_thread_list_items;
- while(running) {
- new_thread_list_items.clear();
- auto start_time = std::chrono::steady_clock::now();
-
- std::string board_url = get_board_url();
- if(!board_url.empty()) {
- PluginResult plugin_result = get_threads_internal(board_url, new_thread_list_items);
- if(plugin_result == PluginResult::OK)
- set_board_thread_list(std::move(new_thread_list_items));
- }
-
- auto time_passed = std::chrono::steady_clock::now() - start_time;
- if(time_passed < std::chrono::seconds(15)) {
- auto time_to_sleep = std::chrono::seconds(15) - time_passed;
- std::unique_lock<std::mutex> lock(thread_list_cache_mutex);
- thread_list_update_cv.wait_for(lock, time_to_sleep);
- }
- }
- });
- }
-
- Fourchan::~Fourchan() {
- running = false;
- {
- std::unique_lock<std::mutex> lock(thread_list_cache_mutex);
- thread_list_update_cv.notify_one();
- }
- thread_list_update_thread.join();
- }
+static const char *SERVICE_NAME = "4chan";
+namespace QuickMedia {
// Returns empty string on failure to read cookie
static std::string get_pass_id_from_cookies_file(const Path &cookies_filepath) {
std::string file_content;
@@ -63,53 +33,6 @@ namespace QuickMedia {
return strip(file_content.substr(pass_id_index, line_end - pass_id_index));
}
- PluginResult Fourchan::get_front_page(BodyItems &result_items) {
- if(pass_id.empty()) {
- Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
- fprintf(stderr, "Failed to get 4chan cookies filepath\n");
- } else {
- pass_id = get_pass_id_from_cookies_file(cookies_filepath);
- }
- }
-
- std::string server_response;
- if(file_get_content(resources_root + "boards.json", server_response) != 0) {
- fprintf(stderr, "failed to read boards.json\n");
- return PluginResult::ERR;
- }
-
- Json::Value json_root;
- Json::CharReaderBuilder json_builder;
- std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
- std::string json_errors;
- if(!json_reader->parse(server_response.data(), server_response.data() + server_response.size(), &json_root, &json_errors)) {
- fprintf(stderr, "4chan front page json error: %s\n", json_errors.c_str());
- return PluginResult::ERR;
- }
-
- if(!json_root.isObject())
- return PluginResult::ERR;
-
- const Json::Value &boards = json_root["boards"];
- if(boards.isArray()) {
- for(const Json::Value &board : boards) {
- const Json::Value &board_id = board["board"]; // /g/, /a/, /b/ etc
- const Json::Value &board_title = board["title"];
- const Json::Value &board_description = board["meta_description"];
- if(board_id.isString() && board_title.isString() && board_description.isString()) {
- std::string board_description_str = board_description.asString();
- html_unescape_sequences(board_description_str);
- auto body_item = BodyItem::create("/" + board_id.asString() + "/ " + board_title.asString());
- body_item->url = board_id.asString();
- result_items.emplace_back(std::move(body_item));
- }
- }
- }
-
- return PluginResult::OK;
- }
-
struct CommentPiece {
enum class Type {
TEXT,
@@ -210,270 +133,74 @@ namespace QuickMedia {
tidyRelease(doc);
}
- PluginResult Fourchan::get_threads_internal(const std::string &url, BodyItems &result_items) {
+ PluginResult FourchanBoardsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
Json::Value json_root;
DownloadResult result = download_json(json_root, fourchan_url + url + "/catalog.json", {}, true);
if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
- if(json_root.isArray()) {
- for(const Json::Value &page_data : json_root) {
- if(!page_data.isObject())
- continue;
-
- const Json::Value &threads = page_data["threads"];
- if(!threads.isArray())
- continue;
-
- for(const Json::Value &thread : threads) {
- if(!thread.isObject())
- continue;
-
- const Json::Value &sub = thread["sub"];
- const char *sub_begin = "";
- const char *sub_end = sub_begin;
- sub.getString(&sub_begin, &sub_end);
-
- const Json::Value &com = thread["com"];
- const char *comment_begin = "";
- const char *comment_end = comment_begin;
- com.getString(&comment_begin, &comment_end);
-
- const Json::Value &thread_num = thread["no"];
- if(!thread_num.isNumeric())
- continue;
-
- std::string title_text;
- extract_comment_pieces(sub_begin, sub_end - sub_begin,
- [&title_text](const CommentPiece &cp) {
- switch(cp.type) {
- case CommentPiece::Type::TEXT:
- title_text.append(cp.text.data, cp.text.size);
- break;
- case CommentPiece::Type::QUOTE:
- title_text += '>';
- title_text.append(cp.text.data, cp.text.size);
- //comment_text += '\n';
- break;
- case CommentPiece::Type::QUOTELINK: {
- title_text.append(cp.text.data, cp.text.size);
- break;
- }
- case CommentPiece::Type::LINE_CONTINUE: {
- if(!title_text.empty() && title_text.back() == '\n') {
- title_text.pop_back();
- }
- break;
- }
- }
- }
- );
- if(!title_text.empty() && title_text.back() == '\n')
- title_text.back() = ' ';
- html_unescape_sequences(title_text);
-
- std::string comment_text;
- extract_comment_pieces(comment_begin, comment_end - comment_begin,
- [&comment_text](const CommentPiece &cp) {
- switch(cp.type) {
- case CommentPiece::Type::TEXT:
- comment_text.append(cp.text.data, cp.text.size);
- break;
- case CommentPiece::Type::QUOTE:
- comment_text += '>';
- comment_text.append(cp.text.data, cp.text.size);
- //comment_text += '\n';
- break;
- case CommentPiece::Type::QUOTELINK: {
- comment_text.append(cp.text.data, cp.text.size);
- break;
- }
- case CommentPiece::Type::LINE_CONTINUE: {
- if(!comment_text.empty() && comment_text.back() == '\n') {
- comment_text.pop_back();
- }
- break;
- }
- }
- }
- );
- html_unescape_sequences(comment_text);
- // TODO: Do the same when wrapping is implemented
- // TODO: Remove this
- int num_lines = 0;
- for(size_t i = 0; i < comment_text.size(); ++i) {
- if(comment_text[i] == '\n') {
- ++num_lines;
- if(num_lines == 6) {
- comment_text = comment_text.substr(0, i) + " (...)";
- break;
- }
- }
- }
- auto body_item = BodyItem::create(std::move(comment_text));
- body_item->set_author(std::move(title_text));
- body_item->url = std::to_string(thread_num.asInt64());
-
- const Json::Value &ext = thread["ext"];
- const Json::Value &tim = thread["tim"];
- if(tim.isNumeric() && ext.isString()) {
- std::string ext_str = ext.asString();
- if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") {
- } else {
- fprintf(stderr, "TODO: Support file extension: %s\n", ext_str.c_str());
- }
- // "s" means small, that's the url 4chan uses for thumbnails.
- // thumbnails always has .jpg extension even if they are gifs or webm.
- body_item->thumbnail_url = fourchan_image_url + url + "/" + std::to_string(tim.asInt64()) + "s.jpg";
- }
-
- result_items.emplace_back(std::move(body_item));
- }
- }
- }
-
- return PluginResult::OK;
- }
-
- void Fourchan::set_board_url(const std::string &new_url) {
- {
- std::lock_guard<std::mutex> lock(board_url_mutex);
- if(current_board_url == new_url)
- return;
- current_board_url = new_url;
- }
-
- std::lock_guard<std::mutex> thread_list_lock(thread_list_cache_mutex);
- thread_list_update_cv.notify_one();
- thread_list_cached = false;
- }
-
- std::string Fourchan::get_board_url() {
- std::lock_guard<std::mutex> lock(board_url_mutex);
- return current_board_url;
- }
-
- void Fourchan::set_board_thread_list(BodyItems body_items) {
- {
- std::lock_guard<std::mutex> lock(board_list_mutex);
- cached_thread_list_items.clear();
- for(auto &body_item : body_items) {
- cached_thread_list_items.push_back(std::move(body_item));
- }
- }
-
- std::unique_lock<std::mutex> thread_list_cache_lock(thread_list_cache_mutex);
- if(!thread_list_cached) {
- thread_list_cached = true;
- thread_list_cached_cv.notify_one();
- }
- }
-
- BodyItems Fourchan::get_board_thread_list() {
- std::lock_guard<std::mutex> lock(board_list_mutex);
- BodyItems body_items;
- for(auto &cached_body_item : cached_thread_list_items) {
- body_items.push_back(std::make_shared<BodyItem>(*cached_body_item));
- }
- return body_items;
- }
-
- PluginResult Fourchan::get_threads(const std::string &url, BodyItems &result_items) {
- set_board_url(url);
-
- std::unique_lock<std::mutex> lock(thread_list_cache_mutex);
- if(!thread_list_cached) {
- if(thread_list_cached_cv.wait_for(lock, std::chrono::seconds(10)) == std::cv_status::timeout)
- return PluginResult::NET_ERR;
- }
-
- result_items = get_board_thread_list();
- return PluginResult::OK;
- }
-
- // TODO: Merge with get_threads_internal
- PluginResult Fourchan::get_thread_comments(const std::string &list_url, const std::string &url, BodyItems &result_items) {
- cached_media_urls.clear();
-
- Json::Value json_root;
- DownloadResult result = download_json(json_root, fourchan_url + list_url + "/thread/" + url + ".json", {}, true);
- if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
-
- if(!json_root.isObject())
+ if(!json_root.isArray())
return PluginResult::ERR;
- std::unordered_map<int64_t, size_t> comment_by_postno;
+ BodyItems result_items;
- const Json::Value &posts = json_root["posts"];
- if(posts.isArray()) {
- for(const Json::Value &post : posts) {
- if(!post.isObject())
- continue;
+ for(const Json::Value &page_data : json_root) {
+ if(!page_data.isObject())
+ continue;
- const Json::Value &post_num = post["no"];
- if(!post_num.isNumeric())
- continue;
-
- int64_t post_num_int = post_num.asInt64();
- comment_by_postno[post_num_int] = result_items.size();
- result_items.push_back(BodyItem::create(""));
- result_items.back()->post_number = std::to_string(post_num_int);
- }
- }
+ const Json::Value &threads = page_data["threads"];
+ if(!threads.isArray())
+ continue;
- size_t body_item_index = 0;
- if(posts.isArray()) {
- for(const Json::Value &post : posts) {
- if(!post.isObject())
+ for(const Json::Value &thread : threads) {
+ if(!thread.isObject())
continue;
- const Json::Value &sub = post["sub"];
+ const Json::Value &sub = thread["sub"];
const char *sub_begin = "";
const char *sub_end = sub_begin;
sub.getString(&sub_begin, &sub_end);
- const Json::Value &com = post["com"];
+ const Json::Value &com = thread["com"];
const char *comment_begin = "";
const char *comment_end = comment_begin;
com.getString(&comment_begin, &comment_end);
- const Json::Value &post_num = post["no"];
- if(!post_num.isNumeric())
+ const Json::Value &thread_num = thread["no"];
+ if(!thread_num.isNumeric())
continue;
- const Json::Value &author = post["name"];
- std::string author_str = "Anonymous";
- if(author.isString())
- author_str = author.asString();
-
- std::string comment_text;
+ std::string title_text;
extract_comment_pieces(sub_begin, sub_end - sub_begin,
- [&comment_text](const CommentPiece &cp) {
+ [&title_text](const CommentPiece &cp) {
switch(cp.type) {
case CommentPiece::Type::TEXT:
- comment_text.append(cp.text.data, cp.text.size);
+ title_text.append(cp.text.data, cp.text.size);
break;
case CommentPiece::Type::QUOTE:
- comment_text += '>';
- comment_text.append(cp.text.data, cp.text.size);
+ title_text += '>';
+ title_text.append(cp.text.data, cp.text.size);
//comment_text += '\n';
break;
case CommentPiece::Type::QUOTELINK: {
- comment_text.append(cp.text.data, cp.text.size);
+ title_text.append(cp.text.data, cp.text.size);
break;
}
case CommentPiece::Type::LINE_CONTINUE: {
- if(!comment_text.empty() && comment_text.back() == '\n') {
- comment_text.pop_back();
+ if(!title_text.empty() && title_text.back() == '\n') {
+ title_text.pop_back();
}
break;
}
}
}
);
- if(!comment_text.empty())
- comment_text += '\n';
+ if(!title_text.empty() && title_text.back() == '\n')
+ title_text.back() = ' ';
+ html_unescape_sequences(title_text);
+
+ std::string comment_text;
extract_comment_pieces(comment_begin, comment_end - comment_begin,
- [&comment_text, &comment_by_postno, &result_items, body_item_index](const CommentPiece &cp) {
+ [&comment_text](const CommentPiece &cp) {
switch(cp.type) {
case CommentPiece::Type::TEXT:
comment_text.append(cp.text.data, cp.text.size);
@@ -485,13 +212,6 @@ namespace QuickMedia {
break;
case CommentPiece::Type::QUOTELINK: {
comment_text.append(cp.text.data, cp.text.size);
- auto it = comment_by_postno.find(cp.quote_postnumber);
- if(it == comment_by_postno.end()) {
- // TODO: Link this quote to a 4chan archive that still has the quoted comment (if available)
- comment_text += "(dead)";
- } else {
- result_items[it->second]->replies.push_back(body_item_index);
- }
break;
}
case CommentPiece::Type::LINE_CONTINUE: {
@@ -503,15 +223,25 @@ namespace QuickMedia {
}
}
);
- if(!comment_text.empty() && comment_text.back() == '\n')
- comment_text.back() = ' ';
html_unescape_sequences(comment_text);
- BodyItem *body_item = result_items[body_item_index].get();
- body_item->set_title(std::move(comment_text));
- body_item->set_author(std::move(author_str));
+ // TODO: Do the same when wrapping is implemented
+ // TODO: Remove this
+ int num_lines = 0;
+ for(size_t i = 0; i < comment_text.size(); ++i) {
+ if(comment_text[i] == '\n') {
+ ++num_lines;
+ if(num_lines == 6) {
+ comment_text = comment_text.substr(0, i) + " (...)";
+ break;
+ }
+ }
+ }
+ auto body_item = BodyItem::create(std::move(comment_text));
+ body_item->set_author(std::move(title_text));
+ body_item->url = std::to_string(thread_num.asInt64());
- const Json::Value &ext = post["ext"];
- const Json::Value &tim = post["tim"];
+ const Json::Value &ext = thread["ext"];
+ const Json::Value &tim = thread["tim"];
if(tim.isNumeric() && ext.isString()) {
std::string ext_str = ext.asString();
if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") {
@@ -520,76 +250,210 @@ namespace QuickMedia {
}
// "s" means small, that's the url 4chan uses for thumbnails.
// thumbnails always has .jpg extension even if they are gifs or webm.
- std::string tim_str = std::to_string(tim.asInt64());
- body_item->thumbnail_url = fourchan_image_url + list_url + "/" + tim_str + "s.jpg";
- body_item->attached_content_url = fourchan_image_url + list_url + "/" + tim_str + ext_str;
- cached_media_urls.push_back(body_item->attached_content_url);
+ body_item->thumbnail_url = fourchan_image_url + url + "/" + std::to_string(tim.asInt64()) + "s.jpg";
}
- ++body_item_index;
+ result_items.push_back(std::move(body_item));
}
}
+ auto body = create_body();
+ body->items = std::move(result_items);
+ body->draw_thumbnails = true;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadListPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
return PluginResult::OK;
}
- PostResult Fourchan::post_comment(const std::string &board, const std::string &thread, const std::string &captcha_id, const std::string &comment) {
- std::string url = "https://sys.4chan.org/" + board + "/post";
+ void FourchanBoardsPage::get_boards(BodyItems &result_items) {
+ std::string server_response;
+ if(file_get_content(resources_root + "boards.json", server_response) != 0) {
+ fprintf(stderr, "failed to read boards.json\n");
+ return;
+ }
- std::vector<CommandArg> additional_args = {
- CommandArg{"-H", "Referer: https://boards.4chan.org/"},
- CommandArg{"-H", "Origin: https://boards.4chan.org"},
- CommandArg{"-F", "resto=" + thread},
- CommandArg{"-F", "com=" + comment},
- CommandArg{"-F", "mode=regist"}
- };
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(server_response.data(), server_response.data() + server_response.size(), &json_root, &json_errors)) {
+ fprintf(stderr, "4chan front page json error: %s\n", json_errors.c_str());
+ return;
+ }
- if(pass_id.empty()) {
- additional_args.push_back(CommandArg{"-F", "g-recaptcha-response=" + captcha_id});
- } else {
- Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
- fprintf(stderr, "Failed to get 4chan cookies filepath\n");
- return PostResult::ERR;
- } else {
- additional_args.push_back(CommandArg{"-c", cookies_filepath.data});
- additional_args.push_back(CommandArg{"-b", cookies_filepath.data});
+ if(!json_root.isObject())
+ return;
+
+ const Json::Value &boards = json_root["boards"];
+ if(!boards.isArray())
+ return;
+
+ for(const Json::Value &board : boards) {
+ const Json::Value &board_id = board["board"]; // /g/, /a/, /b/ etc
+ const Json::Value &board_title = board["title"];
+ const Json::Value &board_description = board["meta_description"];
+ if(board_id.isString() && board_title.isString() && board_description.isString()) {
+ std::string board_description_str = board_description.asString();
+ html_unescape_sequences(board_description_str);
+ auto body_item = BodyItem::create("/" + board_id.asString() + "/ " + board_title.asString());
+ body_item->url = board_id.asString();
+ result_items.push_back(std::move(body_item));
}
}
-
- std::string response;
- if(download_to_string(url, response, additional_args, use_tor, true) != DownloadResult::OK)
- return PostResult::ERR;
-
- if(response.find("successful") != std::string::npos)
- return PostResult::OK;
- if(response.find("banned") != std::string::npos)
- return PostResult::BANNED;
- if(response.find("try again") != std::string::npos || response.find("No valid captcha") != std::string::npos)
- return PostResult::TRY_AGAIN;
- return PostResult::ERR;
}
- BodyItems Fourchan::get_related_media(const std::string &url) {
- BodyItems body_items;
- auto it = std::find(cached_media_urls.begin(), cached_media_urls.end(), url);
- if(it == cached_media_urls.end())
- return body_items;
-
- ++it;
- for(; it != cached_media_urls.end(); ++it) {
- auto body_item = BodyItem::create("");
- body_item->url = *it;
- body_items.push_back(std::move(body_item));
+ // TODO: Merge with get_threads_internal
+ PluginResult FourchanThreadListPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)title;
+ cached_media_urls.clear();
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, fourchan_url + board_id + "/thread/" + url + ".json", {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ BodyItems result_items;
+ std::unordered_map<int64_t, size_t> comment_by_postno;
+
+ const Json::Value &posts = json_root["posts"];
+ if(!posts.isArray())
+ return PluginResult::OK;
+
+ for(const Json::Value &post : posts) {
+ if(!post.isObject())
+ continue;
+
+ const Json::Value &post_num = post["no"];
+ if(!post_num.isNumeric())
+ continue;
+
+ int64_t post_num_int = post_num.asInt64();
+ comment_by_postno[post_num_int] = result_items.size();
+ result_items.push_back(BodyItem::create(""));
+ result_items.back()->post_number = std::to_string(post_num_int);
}
- return body_items;
+
+ size_t body_item_index = 0;
+ for(const Json::Value &post : posts) {
+ if(!post.isObject())
+ continue;
+
+ const Json::Value &sub = post["sub"];
+ const char *sub_begin = "";
+ const char *sub_end = sub_begin;
+ sub.getString(&sub_begin, &sub_end);
+
+ const Json::Value &com = post["com"];
+ const char *comment_begin = "";
+ const char *comment_end = comment_begin;
+ com.getString(&comment_begin, &comment_end);
+
+ const Json::Value &post_num = post["no"];
+ if(!post_num.isNumeric())
+ continue;
+
+ const Json::Value &author = post["name"];
+ std::string author_str = "Anonymous";
+ if(author.isString())
+ author_str = author.asString();
+
+ std::string comment_text;
+ extract_comment_pieces(sub_begin, sub_end - sub_begin,
+ [&comment_text](const CommentPiece &cp) {
+ switch(cp.type) {
+ case CommentPiece::Type::TEXT:
+ comment_text.append(cp.text.data, cp.text.size);
+ break;
+ case CommentPiece::Type::QUOTE:
+ comment_text += '>';
+ comment_text.append(cp.text.data, cp.text.size);
+ //comment_text += '\n';
+ break;
+ case CommentPiece::Type::QUOTELINK: {
+ comment_text.append(cp.text.data, cp.text.size);
+ break;
+ }
+ case CommentPiece::Type::LINE_CONTINUE: {
+ if(!comment_text.empty() && comment_text.back() == '\n') {
+ comment_text.pop_back();
+ }
+ break;
+ }
+ }
+ }
+ );
+ if(!comment_text.empty())
+ comment_text += '\n';
+ extract_comment_pieces(comment_begin, comment_end - comment_begin,
+ [&comment_text, &comment_by_postno, &result_items, body_item_index](const CommentPiece &cp) {
+ switch(cp.type) {
+ case CommentPiece::Type::TEXT:
+ comment_text.append(cp.text.data, cp.text.size);
+ break;
+ case CommentPiece::Type::QUOTE:
+ comment_text += '>';
+ comment_text.append(cp.text.data, cp.text.size);
+ //comment_text += '\n';
+ break;
+ case CommentPiece::Type::QUOTELINK: {
+ comment_text.append(cp.text.data, cp.text.size);
+ auto it = comment_by_postno.find(cp.quote_postnumber);
+ if(it == comment_by_postno.end()) {
+ // TODO: Link this quote to a 4chan archive that still has the quoted comment (if available)
+ comment_text += "(dead)";
+ } else {
+ result_items[it->second]->replies.push_back(body_item_index);
+ }
+ break;
+ }
+ case CommentPiece::Type::LINE_CONTINUE: {
+ if(!comment_text.empty() && comment_text.back() == '\n') {
+ comment_text.pop_back();
+ }
+ break;
+ }
+ }
+ }
+ );
+ if(!comment_text.empty() && comment_text.back() == '\n')
+ comment_text.back() = ' ';
+ html_unescape_sequences(comment_text);
+ BodyItem *body_item = result_items[body_item_index].get();
+ body_item->set_title(std::move(comment_text));
+ body_item->set_author(std::move(author_str));
+
+ const Json::Value &ext = post["ext"];
+ const Json::Value &tim = post["tim"];
+ if(tim.isNumeric() && ext.isString()) {
+ std::string ext_str = ext.asString();
+ if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") {
+ } else {
+ fprintf(stderr, "TODO: Support file extension: %s\n", ext_str.c_str());
+ }
+ // "s" means small, that's the url 4chan uses for thumbnails.
+ // thumbnails always has .jpg extension even if they are gifs or webm.
+ std::string tim_str = std::to_string(tim.asInt64());
+ body_item->thumbnail_url = fourchan_image_url + board_id + "/" + tim_str + "s.jpg";
+ body_item->attached_content_url = fourchan_image_url + board_id + "/" + tim_str + ext_str;
+ cached_media_urls.push_back(body_item->attached_content_url);
+ }
+
+ ++body_item_index;
+ }
+
+ auto body = create_body();
+ body->items = std::move(result_items);
+ body->draw_thumbnails = true;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadPage>(program, board_id, url, std::move(cached_media_urls)), nullptr});
+ return PluginResult::OK;
}
- PluginResult Fourchan::login(const std::string &token, const std::string &pin, std::string &response_msg) {
+ PluginResult FourchanThreadPage::login(const std::string &token, const std::string &pin, std::string &response_msg) {
response_msg.clear();
Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
+ if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) {
fprintf(stderr, "Failed to get 4chan cookies filepath\n");
return PluginResult::ERR;
}
@@ -626,7 +490,52 @@ namespace QuickMedia {
}
}
- const std::string& Fourchan::get_pass_id() const {
+ PostResult FourchanThreadPage::post_comment(const std::string &captcha_id, const std::string &comment) {
+ std::string url = "https://sys.4chan.org/" + board_id + "/post";
+
+ std::vector<CommandArg> additional_args = {
+ CommandArg{"-H", "Referer: https://boards.4chan.org/"},
+ CommandArg{"-H", "Origin: https://boards.4chan.org"},
+ CommandArg{"-F", "resto=" + thread_id},
+ CommandArg{"-F", "com=" + comment},
+ CommandArg{"-F", "mode=regist"}
+ };
+
+ if(pass_id.empty()) {
+ additional_args.push_back(CommandArg{"-F", "g-recaptcha-response=" + captcha_id});
+ } else {
+ Path cookies_filepath;
+ if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) {
+ fprintf(stderr, "Failed to get 4chan cookies filepath\n");
+ return PostResult::ERR;
+ } else {
+ additional_args.push_back(CommandArg{"-c", cookies_filepath.data});
+ additional_args.push_back(CommandArg{"-b", cookies_filepath.data});
+ }
+ }
+
+ std::string response;
+ if(download_to_string(url, response, additional_args, is_tor_enabled(), true) != DownloadResult::OK)
+ return PostResult::ERR;
+
+ if(response.find("successful") != std::string::npos)
+ return PostResult::OK;
+ if(response.find("banned") != std::string::npos)
+ return PostResult::BANNED;
+ if(response.find("try again") != std::string::npos || response.find("No valid captcha") != std::string::npos)
+ return PostResult::TRY_AGAIN;
+ return PostResult::ERR;
+ }
+
+ const std::string& FourchanThreadPage::get_pass_id() {
+ if(pass_id.empty()) {
+ Path cookies_filepath;
+ if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) {
+ fprintf(stderr, "Failed to get 4chan cookies filepath\n");
+ } else {
+ pass_id = get_pass_id_from_cookies_file(cookies_filepath);
+ }
+ }
return pass_id;
}
} \ No newline at end of file
diff --git a/src/plugins/ImageBoard.cpp b/src/plugins/ImageBoard.cpp
new file mode 100644
index 0000000..ac05f80
--- /dev/null
+++ b/src/plugins/ImageBoard.cpp
@@ -0,0 +1,30 @@
+#include "../../plugins/ImageBoard.hpp"
+
+namespace QuickMedia {
+ BodyItems ImageBoardThreadPage::get_related_media(const std::string &url) {
+ BodyItems body_items;
+ auto it = std::find(cached_media_urls.begin(), cached_media_urls.end(), url);
+ if(it == cached_media_urls.end())
+ return body_items;
+
+ ++it;
+ for(; it != cached_media_urls.end(); ++it) {
+ auto body_item = BodyItem::create("");
+ body_item->url = *it;
+ body_items.push_back(std::move(body_item));
+ }
+ return body_items;
+ }
+
+ PluginResult ImageBoardThreadPage::login(const std::string &token, const std::string &pin, std::string &response_msg) {
+ (void)token;
+ (void)pin;
+ response_msg = "Login is not supported on this image board";
+ return PluginResult::ERR;
+ }
+
+ const std::string& ImageBoardThreadPage::get_pass_id() {
+ static std::string empty_str;
+ return empty_str;
+ }
+} \ No newline at end of file
diff --git a/src/plugins/Manga.cpp b/src/plugins/Manga.cpp
index 6ad11ab..70a1664 100644
--- a/src/plugins/Manga.cpp
+++ b/src/plugins/Manga.cpp
@@ -1,7 +1,12 @@
#include "../../plugins/Manga.hpp"
+#include "../../include/Program.h"
namespace QuickMedia {
- const std::vector<Creator>& Manga::get_creators() const {
- return creators;
+ TrackResult MangaChaptersPage::track(const std::string &str) {
+ const char *args[] = { "automedia", "add", "html", content_url.data(), "--start-after", str.data(), "--name", content_title.data(), nullptr };
+ if(exec_program(args, nullptr, nullptr) == 0)
+ return TrackResult::OK;
+ else
+ return TrackResult::ERR;
}
} \ No newline at end of file
diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp
index 9808654..be2d342 100644
--- a/src/plugins/Mangadex.cpp
+++ b/src/plugins/Mangadex.cpp
@@ -1,6 +1,7 @@
#include "../../plugins/Mangadex.hpp"
#include "../../include/Storage.hpp"
#include "../../include/Notification.hpp"
+#include "../../include/StringUtils.hpp"
#include <quickmedia/HtmlSearch.h>
#include <json/reader.h>
@@ -27,30 +28,96 @@ namespace QuickMedia {
return url.substr(find_index + 9);
}
+ static bool get_cookie_filepath(std::string &cookie_filepath) {
+ Path cookie_path = get_storage_dir().join("cookies");
+ if(create_directory_recursive(cookie_path) != 0) {
+ show_notification("Storage", "Failed to create directory: " + cookie_path.data, Urgency::CRITICAL);
+ return false;
+ }
+ cookie_filepath = cookie_path.join("mangadex.txt").data;
+ return true;
+ }
+
struct BodyItemChapterContext {
BodyItems *body_items;
int prev_chapter_number;
bool *is_last_page;
};
- SearchResult Mangadex::search(const std::string &url, BodyItems &result_items) {
+ // TODO: Implement pagination (go to next page and get all results as well)
+ SearchResult MangadexSearchPage::search(const std::string &str, BodyItems &result_items) {
+ std::string rememberme_token;
+ if(!get_rememberme_token(rememberme_token))
+ return SearchResult::ERR;
+
+ std::string url = "https://mangadex.org/search?title=";
+ url += url_param_encode(str);
+ CommandArg cookie_arg = { "-H", "cookie: mangadex_rememberme_token=" + rememberme_token };
+
+ std::string website_data;
+ if(download_to_string(url, website_data, {std::move(cookie_arg)}, is_tor_enabled(), true) != DownloadResult::OK)
+ return SearchResult::NET_ERR;
+
+ 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, "//a",
+ [](QuickMediaHtmlNode *node, void *userdata) {
+ auto *item_data = (BodyItems*)userdata;
+ const char *href = quickmedia_html_node_get_attribute_value(node, "href");
+ const char *title = quickmedia_html_node_get_attribute_value(node, "title");
+ if(title && href && strncmp(href, "/title/", 7) == 0) {
+ auto item = BodyItem::create(strip(title));
+ item->url = mangadex_url + href;
+ item_data->push_back(std::move(item));
+ }
+ }, &result_items);
+
+ if(result != 0)
+ goto cleanup;
+
+ BodyItemImageContext body_item_image_context;
+ body_item_image_context.body_items = &result_items;
+ body_item_image_context.index = 0;
+
+ result = quickmedia_html_find_nodes_xpath(&html_search, "//img",
+ [](QuickMediaHtmlNode *node, void *userdata) {
+ auto *item_data = (BodyItemImageContext*)userdata;
+ const char *src = quickmedia_html_node_get_attribute_value(node, "src");
+ if(src && strncmp(src, "/images/manga/", 14) == 0 && item_data->index < item_data->body_items->size()) {
+ (*item_data->body_items)[item_data->index]->thumbnail_url = mangadex_url + src;
+ item_data->index++;
+ }
+ }, &body_item_image_context);
+
+ if(result != 0)
+ goto cleanup;
+
+ cleanup:
+ quickmedia_html_search_deinit(&html_search);
+ return result == 0 ? SearchResult::OK : SearchResult::ERR;
+ }
+
+ PluginResult MangadexSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
std::string manga_id = title_url_extract_manga_id(url);
std::string request_url = "https://mangadex.org/api/?id=" + manga_id + "&type=manga";
Json::Value json_root;
DownloadResult result = download_json(json_root, request_url, {}, true);
- if(result != DownloadResult::OK) return download_result_to_search_result(result);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
if(!json_root.isObject())
- return SearchResult::ERR;
+ return PluginResult::ERR;
Json::Value &status_json = json_root["status"];
if(!status_json.isString() || status_json.asString() != "OK")
- return SearchResult::ERR;
+ return PluginResult::ERR;
Json::Value &chapter_json = json_root["chapter"];
if(!chapter_json.isObject())
- return SearchResult::ERR;
+ return PluginResult::ERR;
std::vector<std::pair<std::string, Json::Value>> chapters(chapter_json.size());
/* TODO: Optimize member access */
@@ -74,6 +141,8 @@ namespace QuickMedia {
time_t time_now = time(NULL);
+ auto body = create_body();
+
/* TODO: Pointer */
std::string prev_chapter_number;
for(auto it = chapters.begin(); it != chapters.end(); ++it) {
@@ -106,22 +175,17 @@ namespace QuickMedia {
auto item = BodyItem::create(std::move(chapter_name));
item->url = std::move(chapter_url);
- result_items.push_back(std::move(item));
+ body->items.push_back(std::move(item));
}
- return SearchResult::OK;
- }
- static bool get_cookie_filepath(std::string &cookie_filepath) {
- Path cookie_path = get_storage_dir().join("cookies");
- if(create_directory_recursive(cookie_path) != 0) {
- show_notification("Storage", "Failed to create directory: " + cookie_path.data, Urgency::CRITICAL);
- return false;
- }
- cookie_filepath = cookie_path.join("mangadex.txt").data;
- return true;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<MangadexChaptersPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ if(load_manga_content_storage("mangadex", title, manga_id))
+ return PluginResult::OK;
+ return PluginResult::ERR;
}
- bool Mangadex::get_rememberme_token(std::string &rememberme_token_output) {
+ bool MangadexSearchPage::get_rememberme_token(std::string &rememberme_token_output) {
if(rememberme_token) {
rememberme_token_output = rememberme_token.value();
return true;
@@ -153,93 +217,34 @@ namespace QuickMedia {
return true;
}
- struct BodyItemImageContext {
- BodyItems *body_items;
- size_t index;
- };
-
- // TODO: Implement pagination (go to next page and get all results as well)
- SuggestionResult Mangadex::update_search_suggestions(const std::string &text, BodyItems &result_items) {
- std::string rememberme_token;
- if(!get_rememberme_token(rememberme_token))
- return SuggestionResult::ERR;
-
- std::string url = "https://mangadex.org/search?title=";
- url += url_param_encode(text);
- CommandArg cookie_arg = { "-H", "cookie: mangadex_rememberme_token=" + rememberme_token };
-
- std::string website_data;
- if(download_to_string(url, website_data, {std::move(cookie_arg)}, use_tor, true) != DownloadResult::OK)
- return SuggestionResult::NET_ERR;
-
- 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, "//a",
- [](QuickMediaHtmlNode *node, void *userdata) {
- auto *item_data = (BodyItems*)userdata;
- const char *href = quickmedia_html_node_get_attribute_value(node, "href");
- const char *title = quickmedia_html_node_get_attribute_value(node, "title");
- if(title && href && strncmp(href, "/title/", 7) == 0) {
- auto item = BodyItem::create(strip(title));
- item->url = mangadex_url + href;
- item_data->push_back(std::move(item));
- }
- }, &result_items);
-
- if(result != 0)
- goto cleanup;
-
- BodyItemImageContext body_item_image_context;
- body_item_image_context.body_items = &result_items;
- body_item_image_context.index = 0;
-
- result = quickmedia_html_find_nodes_xpath(&html_search, "//img",
- [](QuickMediaHtmlNode *node, void *userdata) {
- auto *item_data = (BodyItemImageContext*)userdata;
- const char *src = quickmedia_html_node_get_attribute_value(node, "src");
- if(src && strncmp(src, "/images/manga/", 14) == 0 && item_data->index < item_data->body_items->size()) {
- (*item_data->body_items)[item_data->index]->thumbnail_url = mangadex_url + src;
- item_data->index++;
- }
- }, &body_item_image_context);
-
- if(result != 0)
- goto cleanup;
-
- cleanup:
- quickmedia_html_search_deinit(&html_search);
- return result == 0 ? SuggestionResult::OK : SuggestionResult::ERR;
+ PluginResult MangadexChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{create_body(), std::make_unique<MangadexImagesPage>(program, content_title, title, url), nullptr});
+ return PluginResult::OK;
}
- ImageResult Mangadex::get_number_of_images(const std::string &url, int &num_images) {
- std::lock_guard<std::mutex> lock(image_urls_mutex);
+ ImageResult MangadexImagesPage::get_number_of_images(int &num_images) {
num_images = 0;
ImageResult image_result = get_image_urls_for_chapter(url);
if(image_result != ImageResult::OK)
return image_result;
- num_images = last_chapter_image_urls.size();
+ num_images = chapter_image_urls.size();
return ImageResult::OK;
}
- bool Mangadex::save_mangadex_cookies(const std::string &url, const std::string &cookie_filepath) {
+ bool MangadexImagesPage::save_mangadex_cookies(const std::string &url, const std::string &cookie_filepath) {
CommandArg cookie_arg = { "-c", cookie_filepath };
std::string server_response;
- if(download_to_string(url, server_response, {std::move(cookie_arg)}, use_tor, true) != DownloadResult::OK)
+ if(download_to_string(url, server_response, {std::move(cookie_arg)}, is_tor_enabled(), true) != DownloadResult::OK)
return false;
return true;
}
- ImageResult Mangadex::get_image_urls_for_chapter(const std::string &url) {
- if(url == last_chapter_url)
+ ImageResult MangadexImagesPage::get_image_urls_for_chapter(const std::string &url) {
+ if(!chapter_image_urls.empty())
return ImageResult::OK;
- last_chapter_image_urls.clear();
-
std::string cookie_filepath;
if(!get_cookie_filepath(cookie_filepath))
return ImageResult::ERR;
@@ -281,38 +286,28 @@ namespace QuickMedia {
continue;
std::string image_url = server + chapter_hash_str + "/" + image_name.asCString();
- last_chapter_image_urls.push_back(std::move(image_url));
+ chapter_image_urls.push_back(std::move(image_url));
}
}
- last_chapter_url = url;
- if(last_chapter_image_urls.empty()) {
- last_chapter_url.clear();
+ if(chapter_image_urls.empty())
return ImageResult::ERR;
- }
return ImageResult::OK;
}
- ImageResult Mangadex::for_each_page_in_chapter(const std::string &chapter_url, PageCallback callback) {
+ ImageResult MangadexImagesPage::for_each_page_in_chapter(PageCallback callback) {
std::vector<std::string> image_urls;
- {
- std::lock_guard<std::mutex> lock(image_urls_mutex);
- ImageResult image_result = get_image_urls_for_chapter(chapter_url);
- if(image_result != ImageResult::OK)
- return image_result;
+ ImageResult image_result = get_image_urls_for_chapter(url);
+ if(image_result != ImageResult::OK)
+ return image_result;
- image_urls = last_chapter_image_urls;
- }
+ image_urls = chapter_image_urls;
for(const std::string &url : image_urls) {
if(!callback(url))
break;
}
- return ImageResult::OK;
- }
- bool Mangadex::extract_id_from_url(const std::string &url, std::string &manga_id) {
- manga_id = title_url_extract_manga_id(url);
- return true;
+ return ImageResult::OK;
}
}
diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp
index 52b9ebd..e96bc65 100644
--- a/src/plugins/Manganelo.cpp
+++ b/src/plugins/Manganelo.cpp
@@ -1,56 +1,10 @@
#include "../../plugins/Manganelo.hpp"
#include "../../include/Notification.hpp"
+#include "../../include/StringUtils.hpp"
#include <quickmedia/HtmlSearch.h>
namespace QuickMedia {
- struct BodyItemImageContext {
- BodyItems *body_items;
- size_t index;
- };
-
- SearchResult Manganelo::search(const std::string &url, BodyItems &result_items) {
- creators.clear();
-
- std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK)
- return SearchResult::NET_ERR;
-
- 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, "//ul[class='row-content-chapter']//a",
- [](QuickMediaHtmlNode *node, void *userdata) {
- auto *item_data = (BodyItems*)userdata;
- const char *href = quickmedia_html_node_get_attribute_value(node, "href");
- const char *text = quickmedia_html_node_get_text(node);
- if(href && text) {
- auto item = BodyItem::create(strip(text));
- item->url = href;
- item_data->push_back(std::move(item));
- }
- }, &result_items);
-
- quickmedia_html_find_nodes_xpath(&html_search, "//a[class='a-h']",
- [](QuickMediaHtmlNode *node, void *userdata) {
- std::vector<Creator> *creators = (std::vector<Creator>*)userdata;
- const char *href = quickmedia_html_node_get_attribute_value(node, "href");
- const char *text = quickmedia_html_node_get_text(node);
- if(href && text && strstr(href, "/author/story/")) {
- Creator creator;
- creator.name = strip(text);
- creator.url = href;
- creators->push_back(std::move(creator));
- }
- }, &creators);
-
- cleanup:
- quickmedia_html_search_deinit(&html_search);
- return result == 0 ? SearchResult::OK : SearchResult::ERR;
- }
-
- // Returns true if changed
+ // Returns true if modified
static bool remove_html_span(std::string &str) {
size_t open_tag_start = str.find("<span");
if(open_tag_start == std::string::npos)
@@ -70,103 +24,103 @@ namespace QuickMedia {
return true;
}
- SuggestionResult Manganelo::update_search_suggestions(const std::string &text, BodyItems &result_items) {
+ SearchResult ManganeloSearchPage::search(const std::string &str, BodyItems &result_items) {
std::string url = "https://manganelo.com/getstorysearchjson";
std::string search_term = "searchword=";
- search_term += url_param_encode(text);
+ search_term += url_param_encode(str);
CommandArg data_arg = { "--data", std::move(search_term) };
Json::Value json_root;
DownloadResult result = download_json(json_root, url, {data_arg}, true);
- if(result != DownloadResult::OK) return download_result_to_suggestion_result(result);
-
- if(json_root.isArray()) {
- for(const Json::Value &child : json_root) {
- if(child.isObject()) {
- Json::Value name = child.get("name", "");
- Json::Value nameunsigned = child.get("nameunsigned", "");
- if(name.isString() && name.asCString()[0] != '\0' && nameunsigned.isString() && nameunsigned.asCString()[0] != '\0') {
- std::string name_str = name.asString();
- while(remove_html_span(name_str)) {}
- auto item = BodyItem::create(strip(name_str));
- item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString());
- Json::Value image = child.get("image", "");
- if(image.isString() && image.asCString()[0] != '\0')
- item->thumbnail_url = image.asString();
- result_items.push_back(std::move(item));
- }
- }
+ if(result != DownloadResult::OK) return download_result_to_search_result(result);
+
+ if(json_root.isNull())
+ return SearchResult::OK;
+
+ if(!json_root.isArray())
+ return SearchResult::ERR;
+
+ for(const Json::Value &child : json_root) {
+ if(!child.isObject())
+ continue;
+
+ Json::Value name = child.get("name", "");
+ Json::Value nameunsigned = child.get("nameunsigned", "");
+ if(name.isString() && name.asCString()[0] != '\0' && nameunsigned.isString() && nameunsigned.asCString()[0] != '\0') {
+ std::string name_str = name.asString();
+ while(remove_html_span(name_str)) {}
+ auto item = BodyItem::create(strip(name_str));
+ item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString());
+ Json::Value image = child.get("image", "");
+ if(image.isString() && image.asCString()[0] != '\0')
+ item->thumbnail_url = image.asString();
+ result_items.push_back(std::move(item));
}
}
- return SuggestionResult::OK;
- }
- ImageResult Manganelo::get_number_of_images(const std::string &url, int &num_images) {
- std::lock_guard<std::mutex> lock(image_urls_mutex);
- num_images = 0;
- ImageResult image_result = get_image_urls_for_chapter(url);
- if(image_result != ImageResult::OK)
- return image_result;
-
- num_images = last_chapter_image_urls.size();
- return ImageResult::OK;
+ return SearchResult::OK;
}
- ImageResult Manganelo::get_image_urls_for_chapter(const std::string &url) {
- if(url == last_chapter_url)
- return ImageResult::OK;
-
- last_chapter_image_urls.clear();
+ PluginResult ManganeloSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ BodyItems chapters_items;
+ std::vector<Creator> creators;
std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK)
- return ImageResult::NET_ERR;
+ if(download_to_string(url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
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, "//div[class='container-chapter-reader']/img",
+ result = quickmedia_html_find_nodes_xpath(&html_search, "//ul[class='row-content-chapter']//a",
[](QuickMediaHtmlNode *node, void *userdata) {
- auto *urls = (std::vector<std::string>*)userdata;
- const char *src = quickmedia_html_node_get_attribute_value(node, "src");
- if(src) {
- std::string image_url = src;
- urls->emplace_back(std::move(image_url));
+ auto *item_data = (BodyItems*)userdata;
+ const char *href = quickmedia_html_node_get_attribute_value(node, "href");
+ const char *text = quickmedia_html_node_get_text(node);
+ if(href && text) {
+ auto item = BodyItem::create(strip(text));
+ item->url = href;
+ item_data->push_back(std::move(item));
+ }
+ }, &chapters_items);
+
+ quickmedia_html_find_nodes_xpath(&html_search, "//a[class='a-h']",
+ [](QuickMediaHtmlNode *node, void *userdata) {
+ std::vector<Creator> *creators = (std::vector<Creator>*)userdata;
+ const char *href = quickmedia_html_node_get_attribute_value(node, "href");
+ const char *text = quickmedia_html_node_get_text(node);
+ if(href && text && strstr(href, "/author/story/")) {
+ Creator creator;
+ creator.name = strip(text);
+ creator.url = href;
+ creators->push_back(std::move(creator));
}
- }, &last_chapter_image_urls);
+ }, &creators);
cleanup:
quickmedia_html_search_deinit(&html_search);
- if(result == 0)
- last_chapter_url = url;
- if(last_chapter_image_urls.empty()) {
- last_chapter_url.clear();
- return ImageResult::ERR;
- }
- return result == 0 ? ImageResult::OK : ImageResult::ERR;
- }
+ if(result != 0)
+ return PluginResult::ERR;
- ImageResult Manganelo::for_each_page_in_chapter(const std::string &chapter_url, PageCallback callback) {
- std::vector<std::string> image_urls;
- {
- std::lock_guard<std::mutex> lock(image_urls_mutex);
- ImageResult image_result = get_image_urls_for_chapter(chapter_url);
- if(image_result != ImageResult::OK)
- return image_result;
+ auto chapters_body = create_body();
+ chapters_body->items = std::move(chapters_items);
+ result_tabs.push_back(Tab{std::move(chapters_body), std::make_unique<ManganeloChaptersPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
- image_urls = last_chapter_image_urls;
+ for(Creator &creator : creators) {
+ result_tabs.push_back(Tab{create_body(), std::make_unique<ManganeloCreatorPage>(program, std::move(creator)), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
}
- for(const std::string &url : image_urls) {
- if(!callback(url))
- break;
+ std::string manga_id;
+ if(extract_id_from_url(url, manga_id)) {
+ if(load_manga_content_storage("manganelo", title, manga_id))
+ return PluginResult::OK;
}
- return ImageResult::OK;
+ return PluginResult::ERR;
}
-
- bool Manganelo::extract_id_from_url(const std::string &url, std::string &manga_id) {
+
+ bool ManganeloSearchPage::extract_id_from_url(const std::string &url, std::string &manga_id) const {
bool manganelo_website = false;
if(url.find("mangakakalot") != std::string::npos || url.find("manganelo") != std::string::npos)
manganelo_website = true;
@@ -177,7 +131,7 @@ namespace QuickMedia {
std::string err_msg = "Url ";
err_msg += url;
err_msg += " doesn't contain manga id";
- show_notification("Manga", err_msg, Urgency::CRITICAL);
+ show_notification("QuickMedia", err_msg, Urgency::CRITICAL);
return false;
}
@@ -186,7 +140,7 @@ namespace QuickMedia {
std::string err_msg = "Url ";
err_msg += url;
err_msg += " doesn't contain manga id";
- show_notification("Manga", err_msg, Urgency::CRITICAL);
+ show_notification("QuickMedia", err_msg, Urgency::CRITICAL);
return false;
}
return true;
@@ -194,56 +148,81 @@ namespace QuickMedia {
std::string err_msg = "Unexpected url ";
err_msg += url;
err_msg += " is not manganelo or mangakakalot";
- show_notification("Manga", err_msg, Urgency::CRITICAL);
+ show_notification("QuickMedia", err_msg, Urgency::CRITICAL);
return false;
}
}
- PluginResult Manganelo::get_creators_manga_list(const std::string &url, BodyItems &result_items) {
+ PluginResult ManganeloChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{create_body(), std::make_unique<ManganeloImagesPage>(program, content_title, title, url), nullptr});
+ return PluginResult::OK;
+ }
+
+ PluginResult ManganeloCreatorPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)title;
+ (void)url;
+ (void)result_tabs;
+ // TODO: Implement
+ return PluginResult::ERR;
+ }
+
+ ImageResult ManganeloImagesPage::get_number_of_images(int &num_images) {
+ num_images = 0;
+ ImageResult image_result = get_image_urls_for_chapter(url);
+ if(image_result != ImageResult::OK)
+ return image_result;
+
+ num_images = chapter_image_urls.size();
+ return ImageResult::OK;
+ }
+
+ ImageResult ManganeloImagesPage::for_each_page_in_chapter(PageCallback callback) {
+ std::vector<std::string> image_urls;
+ ImageResult image_result = get_image_urls_for_chapter(url);
+ if(image_result != ImageResult::OK)
+ return image_result;
+
+ image_urls = chapter_image_urls;
+
+ for(const std::string &url : image_urls) {
+ if(!callback(url))
+ break;
+ }
+
+ return ImageResult::OK;
+ }
+
+ ImageResult ManganeloImagesPage::get_image_urls_for_chapter(const std::string &url) {
+ if(!chapter_image_urls.empty())
+ return ImageResult::OK;
+
std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
+ if(download_to_string(url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK)
+ return ImageResult::NET_ERR;
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, "//div[class='search-story-item']//a[class='item-img']",
- [](QuickMediaHtmlNode *node, void *userdata) {
- auto *item_data = (BodyItems*)userdata;
- const char *href = quickmedia_html_node_get_attribute_value(node, "href");
- const char *title = quickmedia_html_node_get_attribute_value(node, "title");
- if(href && title && strstr(href, "/manga/")) {
- auto body_item = BodyItem::create(title);
- body_item->url = href;
- item_data->push_back(std::move(body_item));
- }
- }, &result_items);
-
- if(result != 0)
- goto cleanup;
-
- BodyItemImageContext body_item_image_context;
- body_item_image_context.body_items = &result_items;
- body_item_image_context.index = 0;
-
- result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='search-story-item']//a[class='item-img']//img",
+ result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='container-chapter-reader']/img",
[](QuickMediaHtmlNode *node, void *userdata) {
- auto *item_data = (BodyItemImageContext*)userdata;
+ auto *urls = (std::vector<std::string>*)userdata;
const char *src = quickmedia_html_node_get_attribute_value(node, "src");
- if(src && item_data->index < item_data->body_items->size()) {
- (*item_data->body_items)[item_data->index]->thumbnail_url = src;
- item_data->index++;
+ if(src) {
+ std::string image_url = src;
+ urls->push_back(std::move(image_url));
}
- }, &body_item_image_context);
+ }, &chapter_image_urls);
cleanup:
quickmedia_html_search_deinit(&html_search);
if(result != 0) {
- result_items.clear();
- return PluginResult::ERR;
+ chapter_image_urls.clear();
+ return ImageResult::ERR;
}
- return PluginResult::OK;
+ if(chapter_image_urls.empty())
+ return ImageResult::ERR;
+ return ImageResult::OK;
}
} \ No newline at end of file
diff --git a/src/plugins/Mangatown.cpp b/src/plugins/Mangatown.cpp
index 400d1ef..5d6f97f 100644
--- a/src/plugins/Mangatown.cpp
+++ b/src/plugins/Mangatown.cpp
@@ -1,59 +1,26 @@
#include "../../plugins/Mangatown.hpp"
#include "../../include/Notification.hpp"
+#include "../../include/StringUtils.hpp"
#include <quickmedia/HtmlSearch.h>
static const std::string mangatown_url = "https://www.mangatown.com";
namespace QuickMedia {
- SearchResult Mangatown::search(const std::string &url, BodyItems &result_items) {
- std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK)
- return SearchResult::NET_ERR;
-
- 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, "//ul[class='chapter_list']//a",
- [](QuickMediaHtmlNode *node, void *userdata) {
- auto *item_data = (BodyItems*)userdata;
- const char *href = quickmedia_html_node_get_attribute_value(node, "href");
- const char *text = quickmedia_html_node_get_text(node);
- if(href && text && strncmp(href, "/manga/", 7) == 0) {
- auto item = BodyItem::create(strip(text));
- item->url = mangatown_url + href;
- item_data->push_back(std::move(item));
- }
- }, &result_items);
-
- cleanup:
- quickmedia_html_search_deinit(&html_search);
-
- int chapter_num = result_items.size();
- for(auto &body_item : result_items) {
- body_item->set_title("Ch. " + std::to_string(chapter_num));
- chapter_num--;
- }
-
- return result == 0 ? SearchResult::OK : SearchResult::ERR;
+ static bool is_number_with_zero_fill(const char *str) {
+ while(*str == '0') { ++str; }
+ return atoi(str) != 0;
}
- struct BodyItemImageContext {
- BodyItems *body_items;
- size_t index;
- };
-
- SuggestionResult Mangatown::update_search_suggestions(const std::string &text, BodyItems &result_items) {
+ SearchResult MangatownSearchPage::search(const std::string &str, BodyItems &result_items) {
std::string url = "https://www.mangatown.com/search?name=";
- url += url_param_encode(text);
+ url += url_param_encode(str);
std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK)
- return SuggestionResult::NET_ERR;
+ if(download_to_string(url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK)
+ return SearchResult::NET_ERR;
if(website_data.empty())
- return SuggestionResult::OK;
+ return SearchResult::OK;
QuickMediaHtmlSearch html_search;
int result = quickmedia_html_search_init(&html_search, website_data.c_str());
@@ -88,77 +55,101 @@ namespace QuickMedia {
cleanup:
quickmedia_html_search_deinit(&html_search);
- return SuggestionResult::OK;
+ return SearchResult::OK;
}
- static bool is_number_with_zero_fill(const char *str) {
- while(*str == '0') { ++str; }
- return atoi(str) != 0;
- }
-
- ImageResult Mangatown::get_number_of_images(const std::string &url, int &num_images) {
- std::lock_guard<std::mutex> lock(image_urls_mutex);
-
- num_images = last_num_pages;
- if(url == last_chapter_url_num_images)
- return ImageResult::OK;
-
- last_num_pages = 0;
+ PluginResult MangatownSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ BodyItems chapters_items;
std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK)
- return ImageResult::NET_ERR;
+ if(download_to_string(url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
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, "//div[class='page_select']//option",
+ result = quickmedia_html_find_nodes_xpath(&html_search, "//ul[class='chapter_list']//a",
[](QuickMediaHtmlNode *node, void *userdata) {
- int *last_num_pages = (int*)userdata;
- const char *value = quickmedia_html_node_get_attribute_value(node, "value");
+ auto *item_data = (BodyItems*)userdata;
+ const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *text = quickmedia_html_node_get_text(node);
- if(value && strncmp(value, "/manga/", 7) == 0) {
- if(is_number_with_zero_fill(text)) {
- (*last_num_pages)++;
- }
+ if(href && text && strncmp(href, "/manga/", 7) == 0) {
+ auto item = BodyItem::create(strip(text));
+ item->url = mangatown_url + href;
+ item_data->push_back(std::move(item));
}
- }, &last_num_pages);
-
- last_num_pages /= 2;
- num_images = last_num_pages;
+ }, &chapters_items);
cleanup:
quickmedia_html_search_deinit(&html_search);
+ if(result != 0)
+ return PluginResult::ERR;
- if(result == 0)
- last_chapter_url_num_images = url;
- if(last_num_pages == 0) {
- last_chapter_url_num_images.clear();
- return ImageResult::ERR;
+ int chapter_num = chapters_items.size();
+ for(auto &body_item : chapters_items) {
+ body_item->set_title("Ch. " + std::to_string(chapter_num));
+ chapter_num--;
}
- return result == 0 ? ImageResult::OK : ImageResult::ERR;
+
+ auto body = create_body();
+ body->items = std::move(chapters_items);
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<MangatownChaptersPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ std::string manga_id;
+ if(extract_id_from_url(url, manga_id)) {
+ if(load_manga_content_storage("mangatown", title, manga_id))
+ return PluginResult::OK;
+ }
+ return PluginResult::ERR;
+ }
+
+ bool MangatownSearchPage::extract_id_from_url(const std::string &url, std::string &manga_id) const {
+ size_t start_index = url.find("/manga/");
+ if(start_index == std::string::npos)
+ return false;
+
+ start_index += 7;
+ size_t end_index = url.find("/", start_index);
+ if(end_index == std::string::npos) {
+ manga_id = url.substr(start_index);
+ return true;
+ }
+
+ manga_id = url.substr(start_index, end_index - start_index);
+ return true;
}
- ImageResult Mangatown::for_each_page_in_chapter(const std::string &chapter_url, PageCallback callback) {
- int num_pages;
- ImageResult image_result = get_number_of_images(chapter_url, num_pages);
+ PluginResult MangatownChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{create_body(), std::make_unique<MangatownImagesPage>(program, content_title, title, url), nullptr});
+ return PluginResult::OK;
+ }
+
+ ImageResult MangatownImagesPage::get_number_of_images(int &num_images) {
+ num_images = 0;
+ ImageResult image_result = get_image_urls_for_chapter(url);
if(image_result != ImageResult::OK)
return image_result;
- int result = 0;
- int page_index = 1;
+ num_images = chapter_image_urls.size();
+ return ImageResult::OK;
+ }
+
+ ImageResult MangatownImagesPage::for_each_page_in_chapter(PageCallback callback) {
+ int num_pages;
+ ImageResult image_result = get_number_of_images(num_pages);
+ if(image_result != ImageResult::OK)
+ return image_result;
- while(true) {
+ for(const std::string &full_url : chapter_image_urls) {
std::string image_src;
std::string website_data;
- std::string full_url = chapter_url + std::to_string(page_index++) + ".html";
- if(download_to_string_cache(full_url, website_data, {}, use_tor, true) != DownloadResult::OK)
+ if(download_to_string_cache(full_url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK)
break;
QuickMediaHtmlSearch html_search;
- result = quickmedia_html_search_init(&html_search, website_data.c_str());
+ int result = quickmedia_html_search_init(&html_search, website_data.c_str());
if(result != 0)
goto cleanup;
@@ -190,19 +181,46 @@ namespace QuickMedia {
return ImageResult::OK;
}
- bool Mangatown::extract_id_from_url(const std::string &url, std::string &manga_id) {
- size_t start_index = url.find("/manga/");
- if(start_index == std::string::npos)
- return false;
-
- start_index += 7;
- size_t end_index = url.find("/", start_index);
- if(end_index == std::string::npos) {
- manga_id = url.substr(start_index);
- return true;
+ ImageResult MangatownImagesPage::get_image_urls_for_chapter(const std::string &url) {
+ if(!chapter_image_urls.empty())
+ return ImageResult::OK;
+
+ int num_pages = 0;
+
+ std::string website_data;
+ if(download_to_string(url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK)
+ return ImageResult::NET_ERR;
+
+ 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, "//div[class='page_select']//option",
+ [](QuickMediaHtmlNode *node, void *userdata) {
+ int *last_num_pages = (int*)userdata;
+ const char *value = quickmedia_html_node_get_attribute_value(node, "value");
+ const char *text = quickmedia_html_node_get_text(node);
+ if(value && strncmp(value, "/manga/", 7) == 0) {
+ if(is_number_with_zero_fill(text)) {
+ (*last_num_pages)++;
+ }
+ }
+ }, &num_pages);
+
+ num_pages /= 2;
+
+ cleanup:
+ quickmedia_html_search_deinit(&html_search);
+ if(result != 0) {
+ chapter_image_urls.clear();
+ return ImageResult::ERR;
}
-
- manga_id = url.substr(start_index, end_index - start_index);
- return true;
+ if(num_pages == 0)
+ return ImageResult::ERR;
+ for(int i = 0; i < num_pages; ++i) {
+ chapter_image_urls.push_back(url + std::to_string(1 + i) + ".html");
+ }
+ return ImageResult::OK;
}
} \ No newline at end of file
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 2107812..dec4a68 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -1,5 +1,6 @@
#include "../../plugins/Matrix.hpp"
#include "../../include/Storage.hpp"
+#include "../../include/StringUtils.hpp"
#include <json/reader.h>
#include <json/writer.h>
#include <fcntl.h>
@@ -18,6 +19,8 @@
// TODO: Verify if this class really is thread-safe (for example room data fields, user fields, message fields; etc that are updated in /sync)
+static const char* SERVICE_NAME = "matrix";
+
namespace QuickMedia {
std::shared_ptr<UserInfo> RoomData::get_user_by_id(const std::string &user_id) {
std::lock_guard<std::mutex> lock(room_mutex);
@@ -99,10 +102,6 @@ namespace QuickMedia {
return messages;
}
- Matrix::Matrix() : Plugin("matrix") {
-
- }
-
PluginResult Matrix::sync(RoomSyncMessages &room_messages) {
std::vector<CommandArg> additional_args = {
{ "-H", "Authorization: Bearer " + access_token },
@@ -819,10 +818,6 @@ namespace QuickMedia {
return PluginResult::OK;
}
- SearchResult Matrix::search(const std::string&, BodyItems&) {
- return SearchResult::OK;
- }
-
static bool generate_random_characters(char *buffer, int buffer_size) {
int fd = open("/dev/urandom", O_RDONLY);
if(fd == -1) {
@@ -1443,7 +1438,7 @@ namespace QuickMedia {
// TODO: Handle well_known field. The spec says clients SHOULD handle it if its provided
- Path session_path = get_storage_dir().join(name);
+ Path session_path = get_storage_dir().join(SERVICE_NAME);
if(create_directory_recursive(session_path) == 0) {
session_path.join("session.json");
if(!save_json_to_file_atomic(session_path, json_root)) {
@@ -1457,7 +1452,7 @@ namespace QuickMedia {
}
PluginResult Matrix::logout() {
- Path session_path = get_storage_dir().join(name).join("session.json");
+ Path session_path = get_storage_dir().join(SERVICE_NAME).join("session.json");
remove(session_path.data.c_str());
std::vector<CommandArg> additional_args = {
@@ -1530,7 +1525,7 @@ namespace QuickMedia {
}
PluginResult Matrix::load_and_verify_cached_session() {
- Path session_path = get_storage_dir().join(name).join("session.json");
+ Path session_path = get_storage_dir().join(SERVICE_NAME).join("session.json");
std::string session_json_content;
if(file_get_content(session_path, session_json_content) != 0) {
fprintf(stderr, "Info: failed to read matrix session from %s. Either its missing or we failed to read the file\n", session_path.data.c_str());
@@ -1721,4 +1716,28 @@ namespace QuickMedia {
std::lock_guard<std::mutex> lock(room_data_mutex);
room_data_by_id.insert(std::make_pair(room->id, room));
}
+
+ DownloadResult Matrix::download_json(Json::Value &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent, std::string *err_msg) const {
+ std::string server_response;
+ if(download_to_string(url, server_response, std::move(additional_args), use_tor, use_browser_useragent, err_msg == nullptr) != DownloadResult::OK) {
+ if(err_msg)
+ *err_msg = server_response;
+ return DownloadResult::NET_ERR;
+ }
+
+ if(server_response.empty())
+ return DownloadResult::OK;
+
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &result, &json_errors)) {
+ fprintf(stderr, "download_json error: %s\n", json_errors.c_str());
+ if(err_msg)
+ *err_msg = std::move(json_errors);
+ return DownloadResult::ERR;
+ }
+
+ return DownloadResult::OK;
+ }
} \ No newline at end of file
diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp
index e29b2b0..8d0679e 100644
--- a/src/plugins/NyaaSi.cpp
+++ b/src/plugins/NyaaSi.cpp
@@ -1,5 +1,8 @@
#include "../../plugins/NyaaSi.hpp"
#include "../../include/Program.h"
+#include "../../include/Storage.hpp"
+#include "../../include/Notification.hpp"
+#include "../../include/StringUtils.hpp"
#include <quickmedia/HtmlSearch.h>
namespace QuickMedia {
@@ -29,60 +32,19 @@ namespace QuickMedia {
return true;
}
- NyaaSi::NyaaSi() : Plugin("nyaa.si") {
-
- }
-
- NyaaSi::~NyaaSi() {
-
- }
-
static std::shared_ptr<BodyItem> create_front_page_item(const std::string &title, const std::string &category) {
auto body_item = BodyItem::create(title);
body_item->url = category;
return body_item;
}
- PluginResult NyaaSi::get_front_page(BodyItems &result_items) {
- result_items.push_back(create_front_page_item("All categories", "0_0"));
- result_items.push_back(create_front_page_item("Anime", "1_0"));
- result_items.push_back(create_front_page_item(" Anime - Music video", "1_1"));
- result_items.push_back(create_front_page_item(" Anime - English translated", "1_2"));
- result_items.push_back(create_front_page_item(" Anime - Non-english translated", "1_3"));
- result_items.push_back(create_front_page_item(" Anime - Raw", "1_4"));
- result_items.push_back(create_front_page_item("Audio", "2_0"));
- result_items.push_back(create_front_page_item(" Audio - Lossless", "2_1"));
- result_items.push_back(create_front_page_item(" Anime - Lossy", "2_2"));
- result_items.push_back(create_front_page_item("Literature", "3_0"));
- result_items.push_back(create_front_page_item(" Literature - English translated", "3_1"));
- result_items.push_back(create_front_page_item(" Literature - Non-english translated", "3_1"));
- result_items.push_back(create_front_page_item(" Literature - Raw", "3_3"));
- result_items.push_back(create_front_page_item("Live Action", "4_0"));
- result_items.push_back(create_front_page_item(" Live Action - English translated", "4_1"));
- result_items.push_back(create_front_page_item(" Live Action - Non-english translated", "4_3"));
- result_items.push_back(create_front_page_item(" Live Action - Idol/Promotional video", "4_2"));
- result_items.push_back(create_front_page_item(" Live Action - Raw", "4_4"));
- result_items.push_back(create_front_page_item("Pictures", "5_0"));
- result_items.push_back(create_front_page_item(" Pictures - Graphics", "5_1"));
- result_items.push_back(create_front_page_item(" Pictures - Photos", "5_2"));
- result_items.push_back(create_front_page_item("Software", "6_0"));
- result_items.push_back(create_front_page_item(" Software - Applications", "6_1"));
- result_items.push_back(create_front_page_item(" Software - Games", "6_2"));
- return PluginResult::OK;
- }
-
-
- SearchResult NyaaSi::content_list_search(const std::string &list_url, const std::string &text, BodyItems &result_items) {
- return search_page(list_url, text, 1, result_items);
- }
-
- SearchResult NyaaSi::content_list_search_page(const std::string &list_url, const std::string &text, int page, BodyItems &result_items) {
- return search_page(list_url, text, 1 + page, result_items);
+ static PluginResult search_result_to_plugin_result(SearchResult search_result) {
+ return (PluginResult)search_result;
}
// TODO: Also show the number of comments for each torrent. TODO: Optimize?
// TODO: Show each field as seperate columns instead of seperating by |
- SearchResult NyaaSi::search_page(const std::string &list_url, const std::string &text, int page, BodyItems &result_items) {
+ static SearchResult search_page(const std::string &list_url, const std::string &text, int page, bool use_tor, BodyItems &result_items) {
std::string full_url = "https://nyaa.si/?c=" + list_url + "&f=0&p=" + std::to_string(page) + "&q=";
full_url += url_param_encode(text);
@@ -217,34 +179,64 @@ namespace QuickMedia {
return SearchResult::OK;
}
- static PluginResult search_result_to_plugin_result(SearchResult search_result) {
- switch(search_result) {
- case SearchResult::OK: return PluginResult::OK;
- case SearchResult::ERR: return PluginResult::ERR;
- case SearchResult::NET_ERR: return PluginResult::NET_ERR;
- }
- return PluginResult::ERR;
+ PluginResult NyaaSiCategoryPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ BodyItems result_items;
+ SearchResult search_result = search_page(url, "", 1, is_tor_enabled(), result_items);
+ if(search_result != SearchResult::OK) return search_result_to_plugin_result(search_result);
+
+ auto body = create_body();
+ body->items = std::move(result_items);
+ body->draw_thumbnails = true;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<NyaaSiSearchPage>(program, strip(title), url), create_search_bar("Search...", 200)});
+ return PluginResult::OK;
}
- PluginResult NyaaSi::get_content_list(const std::string &url, BodyItems &result_items) {
- return search_result_to_plugin_result(search_page(url, "", 1, result_items));
+ void NyaaSiCategoryPage::get_categories(BodyItems &result_items) {
+ result_items.push_back(create_front_page_item("All categories", "0_0"));
+ result_items.push_back(create_front_page_item("Anime", "1_0"));
+ result_items.push_back(create_front_page_item(" Anime - Music video", "1_1"));
+ result_items.push_back(create_front_page_item(" Anime - English translated", "1_2"));
+ result_items.push_back(create_front_page_item(" Anime - Non-english translated", "1_3"));
+ result_items.push_back(create_front_page_item(" Anime - Raw", "1_4"));
+ result_items.push_back(create_front_page_item("Audio", "2_0"));
+ result_items.push_back(create_front_page_item(" Audio - Lossless", "2_1"));
+ result_items.push_back(create_front_page_item(" Anime - Lossy", "2_2"));
+ result_items.push_back(create_front_page_item("Literature", "3_0"));
+ result_items.push_back(create_front_page_item(" Literature - English translated", "3_1"));
+ result_items.push_back(create_front_page_item(" Literature - Non-english translated", "3_1"));
+ result_items.push_back(create_front_page_item(" Literature - Raw", "3_3"));
+ result_items.push_back(create_front_page_item("Live Action", "4_0"));
+ result_items.push_back(create_front_page_item(" Live Action - English translated", "4_1"));
+ result_items.push_back(create_front_page_item(" Live Action - Non-english translated", "4_3"));
+ result_items.push_back(create_front_page_item(" Live Action - Idol/Promotional video", "4_2"));
+ result_items.push_back(create_front_page_item(" Live Action - Raw", "4_4"));
+ result_items.push_back(create_front_page_item("Pictures", "5_0"));
+ result_items.push_back(create_front_page_item(" Pictures - Graphics", "5_1"));
+ result_items.push_back(create_front_page_item(" Pictures - Photos", "5_2"));
+ result_items.push_back(create_front_page_item("Software", "6_0"));
+ result_items.push_back(create_front_page_item(" Software - Applications", "6_1"));
+ result_items.push_back(create_front_page_item(" Software - Games", "6_2"));
}
- struct BodyItemImageContext {
- BodyItems *body_items;
- size_t index;
- };
+ SearchResult NyaaSiSearchPage::search(const std::string &str, BodyItems &result_items) {
+ return search_page(category_id, str, 1, is_tor_enabled(), result_items);
+ }
- PluginResult NyaaSi::get_content_details(const std::string&, const std::string &url, BodyItems &result_items) {
+ PluginResult NyaaSiSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
+ return search_result_to_plugin_result(search_page(category_id, str, 1 + page, is_tor_enabled(), result_items));
+ }
+
+ PluginResult NyaaSiSearchPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
size_t comments_start_index;
std::string title;
+ BodyItems result_items;
auto torrent_item = BodyItem::create("Download magnet");
std::string magnet_url;
std::string description;
std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK)
+ if(download_to_string(url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK)
return PluginResult::NET_ERR;
QuickMediaHtmlSearch html_search;
@@ -377,9 +369,26 @@ namespace QuickMedia {
cleanup:
quickmedia_html_search_deinit(&html_search);
- if(result != 0) {
- result_items.clear();
+ if(result != 0)
return PluginResult::ERR;
+
+ auto body = create_body();
+ body->items = std::move(result_items);
+ body->draw_thumbnails = true;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<NyaaSiTorrentPage>(program), nullptr});
+ return PluginResult::OK;
+ }
+
+ PluginResult NyaaSiTorrentPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)title;
+ (void)result_tabs;
+ if(strncmp(url.c_str(), "magnet:?", 8) == 0) {
+ if(!is_program_executable_by_name("xdg-open")) {
+ show_notification("Nyaa.si", "xdg-utils which provides xdg-open needs to be installed to download torrents", Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+ const char *args[] = { "xdg-open", url.c_str(), nullptr };
+ exec_program_async(args, nullptr);
}
return PluginResult::OK;
}
diff --git a/src/plugins/Page.cpp b/src/plugins/Page.cpp
new file mode 100644
index 0000000..48efeff
--- /dev/null
+++ b/src/plugins/Page.cpp
@@ -0,0 +1,50 @@
+#include "../../plugins/Page.hpp"
+#include "../../include/QuickMedia.hpp"
+#include <json/reader.h>
+
+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<CommandArg> 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) {
+ if(err_msg)
+ *err_msg = server_response;
+ return DownloadResult::NET_ERR;
+ }
+
+ if(server_response.empty())
+ return DownloadResult::OK;
+
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &result, &json_errors)) {
+ fprintf(stderr, "download_json error: %s\n", json_errors.c_str());
+ if(err_msg)
+ *err_msg = std::move(json_errors);
+ return DownloadResult::ERR;
+ }
+
+ return DownloadResult::OK;
+ }
+
+ bool Page::is_tor_enabled() {
+ return program->is_tor_enabled();
+ }
+
+ std::unique_ptr<Body> Page::create_body() {
+ return program->create_body();
+ }
+
+ std::unique_ptr<SearchBar> Page::create_search_bar(const std::string &placeholder_text, int search_delay) {
+ return program->create_search_bar(placeholder_text, search_delay);
+ }
+
+ bool Page::load_manga_content_storage(const char *service_name, const std::string &manga_title, const std::string &manga_id) {
+ return program->load_manga_content_storage(service_name, manga_title, manga_id);
+ }
+} \ No newline at end of file
diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp
index ac60187..3f76b4c 100644
--- a/src/plugins/Plugin.cpp
+++ b/src/plugins/Plugin.cpp
@@ -1,34 +1,10 @@
#include "../../plugins/Plugin.hpp"
+#include "../../include/StringUtils.hpp"
#include <sstream>
#include <iomanip>
#include <array>
-#include <json/reader.h>
namespace QuickMedia {
- SearchResult Plugin::search(const std::string &text, BodyItems &result_items) {
- (void)text;
- (void)result_items;
- return SearchResult::OK;
- }
-
- SuggestionResult Plugin::update_search_suggestions(const std::string &text, BodyItems &result_items) {
- (void)text;
- (void)result_items;
- return SuggestionResult::OK;
- }
-
- SearchResult Plugin::content_list_search(const std::string &list_url, const std::string &text, BodyItems &result_items) {
- (void)list_url;
- (void)text;
- (void)result_items;
- return SearchResult::OK;
- }
-
- BodyItems Plugin::get_related_media(const std::string &url) {
- (void)url;
- return {};
- }
-
struct HtmlEscapeSequence {
char unescape_char;
std::string escape_sequence;
@@ -69,7 +45,7 @@ namespace QuickMedia {
}
}
- std::string Plugin::url_param_encode(const std::string &param) const {
+ std::string url_param_encode(const std::string &param) {
std::ostringstream result;
result.fill('0');
result << std::hex;
@@ -86,24 +62,8 @@ namespace QuickMedia {
return result.str();
}
- DownloadResult Plugin::download_json(Json::Value &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent, std::string *err_msg) const {
- std::string server_response;
- if(download_to_string(url, server_response, std::move(additional_args), use_tor, use_browser_useragent, err_msg == nullptr) != DownloadResult::OK) {
- if(err_msg)
- *err_msg = server_response;
- return DownloadResult::NET_ERR;
- }
-
- Json::CharReaderBuilder json_builder;
- std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
- std::string json_errors;
- if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &result, &json_errors)) {
- fprintf(stderr, "download_json error: %s\n", json_errors.c_str());
- if(err_msg)
- *err_msg = std::move(json_errors);
- return DownloadResult::ERR;
- }
-
- return DownloadResult::OK;
- }
+ SuggestionResult download_result_to_suggestion_result(DownloadResult download_result) { return (SuggestionResult)download_result; }
+ PluginResult download_result_to_plugin_result(DownloadResult download_result) { return (PluginResult)download_result; }
+ SearchResult download_result_to_search_result(DownloadResult download_result) { return (SearchResult)download_result; }
+ ImageResult download_result_to_image_result(DownloadResult download_result) { return (ImageResult)download_result; }
} \ No newline at end of file
diff --git a/src/plugins/Pornhub.cpp b/src/plugins/Pornhub.cpp
index 77d5594..afdd8fc 100644
--- a/src/plugins/Pornhub.cpp
+++ b/src/plugins/Pornhub.cpp
@@ -1,4 +1,5 @@
#include "../../plugins/Pornhub.hpp"
+#include "../../include/StringUtils.hpp"
#include <quickmedia/HtmlSearch.h>
#include <string.h>
@@ -11,14 +12,13 @@ namespace QuickMedia {
return strstr(str, substr);
}
- // TODO: Speed this up by using string.find instead of parsing html
- SuggestionResult Pornhub::update_search_suggestions(const std::string &text, BodyItems &result_items) {
+ SearchResult PornhubSearchPage::search(const std::string &str, BodyItems &result_items) {
std::string url = "https://www.pornhub.com/video/search?search=";
- url += url_param_encode(text);
+ url += url_param_encode(str);
std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor) != DownloadResult::OK)
- return SuggestionResult::NET_ERR;
+ if(download_to_string(url, website_data, {}, is_tor_enabled()) != DownloadResult::OK)
+ return SearchResult::NET_ERR;
struct ItemData {
BodyItems *result_items;
@@ -65,14 +65,21 @@ namespace QuickMedia {
cleanup:
quickmedia_html_search_deinit(&html_search);
- return result == 0 ? SuggestionResult::OK : SuggestionResult::ERR;
+ return result == 0 ? SearchResult::OK : SearchResult::ERR;
}
- BodyItems Pornhub::get_related_media(const std::string &url) {
+ PluginResult PornhubSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)title;
+ (void)url;
+ result_tabs.push_back(Tab{create_body(), std::make_unique<PornhubVideoPage>(program), nullptr});
+ return PluginResult::OK;
+ }
+
+ BodyItems PornhubVideoPage::get_related_media(const std::string &url) {
BodyItems result_items;
std::string website_data;
- if(download_to_string(url, website_data, {}, use_tor) != DownloadResult::OK)
+ if(download_to_string(url, website_data, {}, is_tor_enabled()) != DownloadResult::OK)
return result_items;
struct ItemData {
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index 7e1fc63..40b296d 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -1,22 +1,9 @@
#include "../../plugins/Youtube.hpp"
#include "../../include/Storage.hpp"
-#include <json/reader.h>
-#include <json/writer.h>
#include <string.h>
#include <unordered_set>
namespace QuickMedia {
- static void iterate_suggestion_result(const Json::Value &value, std::vector<std::string> &result_items, int &iterate_count) {
- ++iterate_count;
- if(value.isArray()) {
- for(const Json::Value &child : value) {
- iterate_suggestion_result(child, result_items, iterate_count);
- }
- } else if(value.isString() && iterate_count > 2) {
- result_items.push_back(value.asString());
- }
- }
-
static std::shared_ptr<BodyItem> parse_content_video_renderer(const Json::Value &content_item_json, std::unordered_set<std::string> &added_videos) {
if(!content_item_json.isObject())
return nullptr;
@@ -85,135 +72,6 @@ namespace QuickMedia {
return body_item;
}
- Youtube::Youtube() : Plugin("youtube") {
-
- }
-
- PluginResult Youtube::get_front_page(BodyItems &result_items) {
- bool disabled = true;
- if(disabled)
- return PluginResult::OK;
-
- std::string url = "https://youtube.com/";
-
- std::vector<CommandArg> additional_args = {
- { "-H", "x-spf-referer: " + url },
- { "-H", "x-youtube-client-name: 1" },
- { "-H", "x-youtube-client-version: 2.20200626.03.00" },
- { "-H", "referer: " + url }
- };
-
- //std::vector<CommandArg> cookies = get_cookies();
- //additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
-
- Json::Value json_root;
- DownloadResult result = download_json(json_root, url + "?pbj=1", std::move(additional_args), true);
- if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
-
- if(!json_root.isArray())
- return PluginResult::ERR;
-
- std::unordered_set<std::string> added_videos;
-
- for(const Json::Value &json_item : json_root) {
- if(!json_item.isObject())
- continue;
-
- const Json::Value &response_json = json_item["response"];
- if(!response_json.isObject())
- continue;
-
- const Json::Value &contents_json = response_json["contents"];
- if(!contents_json.isObject())
- continue;
-
- const Json::Value &tcbrr_json = contents_json["twoColumnBrowseResultsRenderer"];
- if(!tcbrr_json.isObject())
- continue;
-
- const Json::Value &tabs_json = tcbrr_json["tabs"];
- if(!tabs_json.isArray())
- continue;
-
- for(const Json::Value &tab_item_json : tabs_json) {
- if(!tab_item_json.isObject())
- continue;
-
- const Json::Value &tab_renderer_json = tab_item_json["tabRenderer"];
- if(!tab_renderer_json.isObject())
- continue;
-
- const Json::Value &content_json = tab_renderer_json["content"];
- if(!content_json.isObject())
- continue;
-
- const Json::Value &rich_grid_renderer = content_json["richGridRenderer"];
- if(!rich_grid_renderer.isObject())
- continue;
-
- const Json::Value &contents2_json = rich_grid_renderer["contents"];
- if(!contents2_json.isArray())
- continue;
-
- for(const Json::Value &contents_item : contents2_json) {
- const Json::Value &rich_item_renderer_json = contents_item["richItemRenderer"];
- if(!rich_item_renderer_json.isObject())
- continue;
-
- const Json::Value &rich_item_contents = rich_item_renderer_json["content"];
- std::shared_ptr<BodyItem> body_item = parse_content_video_renderer(rich_item_contents, added_videos);
- if(body_item)
- result_items.push_back(std::move(body_item));
- }
- }
- }
-
- return PluginResult::OK;
- }
-
- std::string Youtube::autocomplete_search(const std::string &query) {
- // Return the last result if the query is a substring of the autocomplete result
- if(last_autocomplete_result.size() >= query.size() && memcmp(query.data(), last_autocomplete_result.data(), query.size()) == 0)
- return last_autocomplete_result;
-
- 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(query);
-
- std::string server_response;
- if(download_to_string(url, server_response, {}, use_tor, true) != DownloadResult::OK)
- return query;
-
- size_t json_start = server_response.find_first_of('(');
- if(json_start == std::string::npos)
- return query;
- ++json_start;
-
- size_t json_end = server_response.find_last_of(')');
- if(json_end == std::string::npos)
- return query;
-
- if(json_end == 0 || json_start >= json_end)
- return query;
-
- Json::Value json_root;
- Json::CharReaderBuilder json_builder;
- std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
- std::string json_errors;
- if(!json_reader->parse(&server_response[json_start], &server_response[json_end], &json_root, &json_errors)) {
- fprintf(stderr, "Youtube autocomplete search json error: %s\n", json_errors.c_str());
- return query;
- }
-
- int iterate_count = 0;
- std::vector<std::string> result_items;
- iterate_suggestion_result(json_root, result_items, iterate_count);
- if(result_items.empty())
- return query;
-
- last_autocomplete_result = result_items[0];
- return result_items[0];
- }
-
// Returns empty string if continuation token can't be found
static std::string item_section_renderer_get_continuation_token(const Json::Value &item_section_renderer_json) {
const Json::Value &continuations_json = item_section_renderer_json["continuations"];
@@ -277,9 +135,77 @@ namespace QuickMedia {
}
}
- SuggestionResult Youtube::update_search_suggestions(const std::string &text, BodyItems &result_items) {
+ static std::string remove_index_from_playlist_url(const std::string &url) {
+ std::string result = url;
+ size_t index = result.rfind("&index=");
+ if(index == std::string::npos)
+ return result;
+ return result.substr(0, index);
+ }
+
+ static std::shared_ptr<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set<std::string> &added_videos) {
+ const Json::Value &compact_video_renderer_json = item_json["compactVideoRenderer"];
+ if(!compact_video_renderer_json.isObject())
+ return nullptr;
+
+ const Json::Value &video_id_json = compact_video_renderer_json["videoId"];
+ if(!video_id_json.isString())
+ return nullptr;
+
+ std::string video_id_str = video_id_json.asString();
+ if(added_videos.find(video_id_str) != added_videos.end())
+ return nullptr;
+
+ std::string thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg";
+
+ const char *date = nullptr;
+ const Json::Value &published_time_text_json = compact_video_renderer_json["publishedTimeText"];
+ if(published_time_text_json.isObject()) {
+ const Json::Value &text_json = published_time_text_json["simpleText"];
+ if(text_json.isString())
+ date = text_json.asCString();
+ }
+
+ const char *length = nullptr;
+ const Json::Value &length_text_json = compact_video_renderer_json["lengthText"];
+ if(length_text_json.isObject()) {
+ const Json::Value &text_json = length_text_json["simpleText"];
+ if(text_json.isString())
+ length = text_json.asCString();
+ }
+
+ const char *title = nullptr;
+ const Json::Value &title_json = compact_video_renderer_json["title"];
+ if(title_json.isObject()) {
+ const Json::Value &simple_text_json = title_json["simpleText"];
+ if(simple_text_json.isString()) {
+ title = simple_text_json.asCString();
+ }
+ }
+
+ if(!title)
+ return nullptr;
+
+ auto body_item = BodyItem::create(title);
+ /* TODO: Make date a different color */
+ std::string date_str;
+ if(date)
+ date_str += date;
+ if(length) {
+ if(!date_str.empty())
+ date_str += '\n';
+ date_str += length;
+ }
+ body_item->set_description(std::move(date_str));
+ body_item->url = "https://www.youtube.com/watch?v=" + video_id_str;
+ body_item->thumbnail_url = std::move(thumbnail_url);
+ added_videos.insert(video_id_str);
+ return body_item;
+ }
+
+ SearchResult YoutubeSearchPage::search(const std::string &str, BodyItems &result_items) {
std::string url = "https://youtube.com/results?search_query=";
- url += url_param_encode(text);
+ url += url_param_encode(str);
std::vector<CommandArg> additional_args = {
{ "-H", "x-spf-referer: " + url },
@@ -292,11 +218,11 @@ namespace QuickMedia {
//additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
- DownloadResult result = download_json(json_root, url + "?pbj=1", std::move(additional_args), true);
- if(result != DownloadResult::OK) return download_result_to_suggestion_result(result);
+ DownloadResult result = download_json(json_root, url + "&pbj=1", std::move(additional_args), true);
+ if(result != DownloadResult::OK) return download_result_to_search_result(result);
if(!json_root.isArray())
- return SuggestionResult::ERR;
+ return SearchResult::ERR;
std::string continuation_token;
std::unordered_set<std::string> added_videos; /* The input contains duplicates, filter them out! */
@@ -345,10 +271,17 @@ namespace QuickMedia {
if(!continuation_token.empty())
search_suggestions_get_continuation(url, continuation_token, result_items);
- return SuggestionResult::OK;
+ return SearchResult::OK;
+ }
+
+ PluginResult YoutubeSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)title;
+ (void)url;
+ result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeVideoPage>(program), nullptr});
+ return PluginResult::OK;
}
- void Youtube::search_suggestions_get_continuation(const std::string &url, const std::string &continuation_token, BodyItems &result_items) {
+ void YoutubeSearchPage::search_suggestions_get_continuation(const std::string &url, const std::string &continuation_token, BodyItems &result_items) {
std::string next_url = url + "&pbj=1&ctoken=" + continuation_token;
std::vector<CommandArg> additional_args = {
@@ -393,93 +326,9 @@ namespace QuickMedia {
}
}
- std::vector<CommandArg> Youtube::get_cookies() const {
- if(use_tor)
- return {};
-
- Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
- fprintf(stderr, "Warning: Failed to create youtube cookies file\n");
- return {};
- }
-
- return {
- CommandArg{ "-b", cookies_filepath.data },
- CommandArg{ "-c", cookies_filepath.data }
- };
- }
-
- static std::string remove_index_from_playlist_url(const std::string &url) {
- std::string result = url;
- size_t index = result.rfind("&index=");
- if(index == std::string::npos)
- return result;
- return result.substr(0, index);
- }
-
- static std::shared_ptr<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set<std::string> &added_videos) {
- const Json::Value &compact_video_renderer_json = item_json["compactVideoRenderer"];
- if(!compact_video_renderer_json.isObject())
- return nullptr;
-
- const Json::Value &video_id_json = compact_video_renderer_json["videoId"];
- if(!video_id_json.isString())
- return nullptr;
-
- std::string video_id_str = video_id_json.asString();
- if(added_videos.find(video_id_str) != added_videos.end())
- return nullptr;
-
- std::string thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg";
-
- const char *date = nullptr;
- const Json::Value &published_time_text_json = compact_video_renderer_json["publishedTimeText"];
- if(published_time_text_json.isObject()) {
- const Json::Value &text_json = published_time_text_json["simpleText"];
- if(text_json.isString())
- date = text_json.asCString();
- }
-
- const char *length = nullptr;
- const Json::Value &length_text_json = compact_video_renderer_json["lengthText"];
- if(length_text_json.isObject()) {
- const Json::Value &text_json = length_text_json["simpleText"];
- if(text_json.isString())
- length = text_json.asCString();
- }
-
- const char *title = nullptr;
- const Json::Value &title_json = compact_video_renderer_json["title"];
- if(title_json.isObject()) {
- const Json::Value &simple_text_json = title_json["simpleText"];
- if(simple_text_json.isString()) {
- title = simple_text_json.asCString();
- }
- }
-
- if(!title)
- return nullptr;
-
- auto body_item = BodyItem::create(title);
- /* TODO: Make date a different color */
- std::string date_str;
- if(date)
- date_str += date;
- if(length) {
- if(!date_str.empty())
- date_str += '\n';
- date_str += length;
- }
- body_item->set_description(std::move(date_str));
- body_item->url = "https://www.youtube.com/watch?v=" + video_id_str;
- body_item->thumbnail_url = std::move(thumbnail_url);
- added_videos.insert(video_id_str);
- return body_item;
- }
-
// TODO: Make this faster by using string search instead of parsing html.
// TODO: If the result is a play
- BodyItems Youtube::get_related_media(const std::string &url) {
+ BodyItems YoutubeVideoPage::get_related_media(const std::string &url) {
BodyItems result_items;
std::string modified_url = remove_index_from_playlist_url(url);