aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--TODO2
-rw-r--r--include/Path.hpp2
-rw-r--r--include/SearchBar.hpp1
-rw-r--r--include/StringUtils.hpp1
-rw-r--r--plugins/MangaGeneric.hpp25
-rw-r--r--plugins/Matrix.hpp3
-rw-r--r--plugins/Page.hpp2
-rw-r--r--plugins/Soundcloud.hpp9
-rw-r--r--src/QuickMedia.cpp44
-rw-r--r--src/SearchBar.cpp8
-rw-r--r--src/StringUtils.cpp5
-rw-r--r--src/plugins/MangaGeneric.cpp159
-rw-r--r--src/plugins/Matrix.cpp33
-rw-r--r--src/plugins/Soundcloud.cpp68
15 files changed, 278 insertions, 86 deletions
diff --git a/README.md b/README.md
index 32621cf..6cf4502 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ In matrix you can select a message with enter to open the url in the message (or
`/logout` to logout.\
`/leave` to leave the current room.\
`/me [text]` to send a message of type "m.emote".\
-`/react [text]` to react to the selected message.
+`/react [text]` to react to the selected message or if you are replying to message then it reacts to that message.
## Mangadex
To search for manga with mangadex, you need to be logged into mangadex in your browser and copy the `mangadex_rememberme_token` cookie from developer tools
and store it in `$HOME/.config/quickmedia/credentials/mangadex.json` under the key `rememberme_token`. Here is an example what the file should look like:
diff --git a/TODO b/TODO
index d3f56f4..28639ea 100644
--- a/TODO
+++ b/TODO
@@ -90,7 +90,7 @@ Make /logout work everywhere, not only in room message input (or add a logout bu
Add a notifications tab to show messages that mention us in all rooms (and then press enter to go to that message in that room), also add a unread/mentioned rooms list tab to only show rooms with unread messages or mentions.
Disable message input in matrix when muted.
Preview rooms?
-Handle matrix token being invalidated while running and not running.
+Handle matrix token being invalidated while running.
Update upload limit if its updated on the server.
Editing a reply removes reply formatting (both in body and formatted_body). Element also does this when you edit a reply twice. This breaks element mobile that is unable to display replied-to messages without correct formatting (doesn't fetch the replied-to message).
This also removes the mentioned name which breaks mention for reply.
diff --git a/include/Path.hpp b/include/Path.hpp
index 19c8eb9..571fb9e 100644
--- a/include/Path.hpp
+++ b/include/Path.hpp
@@ -39,7 +39,7 @@ namespace QuickMedia {
Path parent() {
size_t slash_index = data.rfind('/');
- if(slash_index != std::string::npos || slash_index == 0)
+ if(slash_index != std::string::npos && slash_index > 0)
return Path(data.substr(0, slash_index));
return Path("/");
}
diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp
index 76ad24b..06c4c98 100644
--- a/include/SearchBar.hpp
+++ b/include/SearchBar.hpp
@@ -70,5 +70,6 @@ namespace QuickMedia {
bool mouse_left_inside;
float vertical_pos;
sf::Clock time_since_search_update;
+ sf::Vector2u prev_window_size;
};
} \ No newline at end of file
diff --git a/include/StringUtils.hpp b/include/StringUtils.hpp
index 97c73dd..e97a423 100644
--- a/include/StringUtils.hpp
+++ b/include/StringUtils.hpp
@@ -13,5 +13,6 @@ namespace QuickMedia {
// Returns the number of replaced substrings
size_t string_replace_all(std::string &str, const std::string &old_str, const std::string &new_str);
std::string strip(const std::string &str);
+ bool string_starts_with(const std::string &str, const char *sub);
bool string_ends_with(const std::string &str, const std::string &ends_with_str);
} \ No newline at end of file
diff --git a/plugins/MangaGeneric.hpp b/plugins/MangaGeneric.hpp
index 98d06d8..a03756d 100644
--- a/plugins/MangaGeneric.hpp
+++ b/plugins/MangaGeneric.hpp
@@ -23,13 +23,19 @@ namespace QuickMedia {
const char *field_name = nullptr;
};
- // If |field_contains| is null, then any matching query is added. If |field_name| is "text", then the inner text is used.
struct ThumbnailQuery {
const char *html_query = nullptr;
const char *field_name = nullptr;
const char *field_contains = nullptr;
};
+ struct AuthorsQuery {
+ const char *html_query = nullptr;
+ const char *title_field = nullptr;
+ const char *url_field = nullptr;
+ const char *url_contains = nullptr;
+ };
+
struct ListChaptersQuery {
const char *html_query = nullptr;
const char *title_field = nullptr;
@@ -92,6 +98,7 @@ namespace QuickMedia {
const char* get_title() const override { return "All"; }
bool search_is_filter() override { return false; }
SearchResult search(const std::string &str, BodyItems &result_items) override;
+ PluginResult get_page(const std::string &url, BodyItems &result_items);
PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
sf::Vector2i get_thumbnail_max_size() override { return sf::Vector2i(101, 141); };
@@ -106,6 +113,9 @@ namespace QuickMedia {
MangaGenericSearchPage& description_handler(std::vector<DescriptionQuery> queries);
// This is optional.
MangaGenericSearchPage& thumbnail_handler(std::vector<ThumbnailQuery> queries);
+ // If |url_contains| is null, then any matching query is added. If |title_field| is "text", then the inner text is used.
+ // This is optional.
+ MangaGenericSearchPage& authors_handler(const char *html_query, const char *title_field, const char *url_field, const char *url_contains);
// If |url_contains| is null, then any matching query is added. If |title_field| is "text", then the inner text is used.
// This is required.
@@ -141,6 +151,7 @@ namespace QuickMedia {
std::vector<TextQuery> text_queries;
std::vector<DescriptionQuery> description_queries;
std::vector<ThumbnailQuery> thumbnail_queries;
+ AuthorsQuery authors_query;
ListChaptersQuery list_chapters_query;
ListPageQuery list_page_query;
MangaIdExtractor manga_id_extractor;
@@ -162,6 +173,18 @@ namespace QuickMedia {
bool fail_on_http_error;
};
+ class MangaGenericCreatorPage : public LazyFetchPage {
+ public:
+ MangaGenericCreatorPage(Program *program, MangaGenericSearchPage *search_page, Creator creator) : LazyFetchPage(program), search_page(search_page), creator(std::move(creator)) {}
+ const char* get_title() const override { return creator.name.c_str(); }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
+ PluginResult lazy_fetch(BodyItems &result_items) override;
+ sf::Vector2i get_thumbnail_max_size() override { return sf::Vector2i(101, 141); };
+ private:
+ MangaGenericSearchPage *search_page;
+ Creator creator;
+ };
+
class MangaGenericImagesPage : public MangaImagesPage {
public:
MangaGenericImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url, const char *service_name, const std::string &website_url, const ListPageQuery *list_page_query, bool fail_on_http_error) :
diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp
index 0659983..f76d70d 100644
--- a/plugins/Matrix.hpp
+++ b/plugins/Matrix.hpp
@@ -477,7 +477,8 @@ namespace QuickMedia {
class Matrix {
public:
- void start_sync(MatrixDelegate *delegate, bool &cached);
+ // TODO: Make this return the Matrix object instead, to force users to call start_sync
+ bool start_sync(MatrixDelegate *delegate, bool &cached);
void stop_sync();
bool is_initial_sync_finished() const;
// Returns true if initial sync failed, and |err_msg| is set to the error reason in that case
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index 5c0a558..6599fe4 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -89,6 +89,8 @@ namespace QuickMedia {
virtual bool search_is_filter() override { return true; }
bool is_lazy_fetch_page() const override { return true; }
virtual PluginResult lazy_fetch(BodyItems &result_items) = 0;
+ // If this returns true then |lazy_fetch| is not meant to return results but async background load the page. This can be used to fetch API keys for example
+ virtual bool lazy_fetch_is_loader() { return false; }
};
class RelatedVideosPage : public Page {
diff --git a/plugins/Soundcloud.hpp b/plugins/Soundcloud.hpp
index e04d409..bb23efb 100644
--- a/plugins/Soundcloud.hpp
+++ b/plugins/Soundcloud.hpp
@@ -7,17 +7,22 @@ namespace QuickMedia {
public:
SoundcloudPage(Program *program) : Page(program) {}
virtual ~SoundcloudPage() = default;
+ virtual const char* get_title() const override { return ""; }
PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
};
- class SoundcloudSearchPage : public SoundcloudPage {
+ class SoundcloudSearchPage : public LazyFetchPage {
public:
- SoundcloudSearchPage(Program *program) : SoundcloudPage(program) {}
+ SoundcloudSearchPage(Program *program) : LazyFetchPage(program), submit_page(program) {}
const char* get_title() const override { return "Search"; }
bool search_is_filter() override { return false; }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
SearchResult search(const std::string &str, BodyItems &result_items) override;
PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
+ PluginResult lazy_fetch(BodyItems &result_items) override;
+ bool lazy_fetch_is_loader() override { return true; }
private:
+ SoundcloudPage submit_page;
std::string query_urn;
};
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index eb82a54..e2ee0ec 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -745,6 +745,7 @@ namespace QuickMedia {
{"//div[id='book_list']//div[class='media']//img", "src", nullptr},
{"//div[id='single_book']//div[class='cover']//img", "src", nullptr}
})
+ .authors_handler("//div[id='single_book']//a[class='author']", "text", "href", "/author/")
.list_chapters_handler("//div[class='chapters']//div[class='chapter']//a", "text", "href", "/manga/")
.list_chapters_uploaded_time_handler("//div[class='chapters']//div[class='update_time']", "text", nullptr)
.list_page_images_custom_handler([](const std::string &html_source) {
@@ -1271,8 +1272,12 @@ namespace QuickMedia {
std::string err_msg;
if(matrix->did_initial_sync_fail(err_msg)) {
show_notification("QuickMedia", "Initial matrix sync failed, error: " + err_msg, Urgency::CRITICAL);
+ matrix->logout();
+ current_page = PageType::CHAT_LOGIN;
+ chat_login_page();
+ after_matrix_login_page();
window.close();
- exit(0);
+ exit(exit_code);
}
}
}
@@ -1703,9 +1708,10 @@ namespace QuickMedia {
FetchResult fetch_result = associated_data.fetch_future.get();
tabs[i].body->items = std::move(fetch_result.body_items);
if(tabs[i].search_bar) tabs[i].body->filter_search_fuzzy(tabs[i].search_bar->get_text());
+ LazyFetchPage *lazy_fetch_page = static_cast<LazyFetchPage*>(tabs[i].page.get());
if(fetch_result.result != PluginResult::OK)
associated_data.search_result_text.setString("Failed to fetch page!");
- else if(tabs[i].body->items.empty())
+ else if(tabs[i].body->items.empty() && !lazy_fetch_page->lazy_fetch_is_loader())
associated_data.search_result_text.setString("No results found");
else
associated_data.search_result_text.setString("");
@@ -4024,7 +4030,12 @@ namespace QuickMedia {
show_notification("QuickMedia", "Error: invalid command: " + text + ", expected /upload, /logout, /me or /react", Urgency::NORMAL);
return false;
}
- }
+ } else if(chat_state == ChatState::REPLYING && text[0] == '/') {
+ if(strncmp(text.c_str(), "/react ", 7) == 0) {
+ msgtype = "m.reaction";
+ text.erase(text.begin(), text.begin() + 7);
+ }
+ }
auto message = std::make_shared<Message>();
message->user = matrix->get_me(current_room);
@@ -4042,10 +4053,14 @@ namespace QuickMedia {
if(tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item() && selected_tab == MESSAGES_TAB_INDEX)
scroll_to_end = true;
- if(chat_state == ChatState::TYPING_MESSAGE) {
+ if(chat_state == ChatState::TYPING_MESSAGE || (chat_state == ChatState::REPLYING && msgtype == "m.reaction")) {
BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected();
+ if(chat_state == ChatState::REPLYING)
+ selected_item = currently_operating_on_item.get();
if(msgtype == "m.reaction" && selected_item) {
void *related_to_message = selected_item->userdata;
+ if(chat_state == ChatState::REPLYING)
+ related_to_message = currently_operating_on_item->userdata;
message->type = MessageType::REACTION;
message->related_event_type = RelatedEventType::REACTION;
message->related_event_id = static_cast<Message*>(related_to_message)->event_id;
@@ -4903,7 +4918,7 @@ namespace QuickMedia {
chat_login_page();
if(current_page == PageType::CHAT)
after_matrix_login_page();
- exit(0);
+ exit(exit_code);
break;
}
default:
@@ -4956,14 +4971,11 @@ namespace QuickMedia {
if(redraw) {
redraw = false;
- float room_name_padding_y = 0.0f;
- float padding_bottom = 0.0f;
if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) {
- room_name_padding_y = std::floor(10.0f * get_ui_scale() + room_name_total_height);
tab_vertical_offset = std::floor(10.0f * get_ui_scale());
}
- tab_shade_height = std::floor(tab_vertical_offset) + Tabs::get_height() + room_name_padding_y + padding_bottom;
+ tab_shade_height = std::floor(tab_vertical_offset) + Tabs::get_height() + room_name_padding_y;
float body_padding_horizontal = 10.0f;
float body_padding_vertical = std::floor(10.0f);
@@ -5237,6 +5249,10 @@ namespace QuickMedia {
std::string err_msg;
if(matrix->did_initial_sync_fail(err_msg)) {
show_notification("QuickMedia", "Initial matrix sync failed, error: " + err_msg, Urgency::CRITICAL);
+ matrix->logout();
+ current_page = PageType::CHAT_LOGIN;
+ chat_login_page();
+ after_matrix_login_page();
window.close();
goto chat_page_end;
}
@@ -5255,6 +5271,10 @@ namespace QuickMedia {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if(matrix->did_initial_sync_fail(err_msg)) {
show_notification("QuickMedia", "Initial matrix sync failed, error: " + err_msg, Urgency::CRITICAL);
+ matrix->logout();
+ current_page = PageType::CHAT_LOGIN;
+ chat_login_page();
+ after_matrix_login_page();
window.close();
goto chat_page_end;
}
@@ -5344,7 +5364,11 @@ namespace QuickMedia {
MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get(), matrix_invites_page.get());
bool sync_cached = false;
- matrix->start_sync(&matrix_handler, sync_cached);
+ if(!matrix->start_sync(&matrix_handler, sync_cached)) {
+ show_notification("QuickMedia", "Failed to start sync", Urgency::CRITICAL);
+ exit_code = 1;
+ return;
+ }
is_login_sync = !sync_cached;
std::vector<Tab> tabs;
diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp
index 6aa6eec..059d028 100644
--- a/src/SearchBar.cpp
+++ b/src/SearchBar.cpp
@@ -55,11 +55,17 @@ namespace QuickMedia {
}
void SearchBar::draw(sf::RenderWindow &window, bool draw_shadow) {
+ sf::Vector2u window_size = window.getSize();
+ if(window_size.x != prev_window_size.x || window_size.y != prev_window_size.y) {
+ needs_update = true;
+ prev_window_size = window_size;
+ }
+
if(needs_update) {
needs_update = false;
- sf::Vector2u window_size = window.getSize();
onWindowResize(sf::Vector2f(window_size.x, window_size.y));
}
+
(void)draw_shadow;
//if(draw_shadow)
// window.draw(background_shadow);
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index 111822e..56d746c 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -67,6 +67,11 @@ namespace QuickMedia {
return str.substr(start, end - start + 1);
}
+ bool string_starts_with(const std::string &str, const char *sub) {
+ size_t sub_len = strlen(sub);
+ return sub_len == 0 || (str.size() >= sub_len && memcmp(str.c_str(), sub, sub_len) == 0);
+ }
+
bool string_ends_with(const std::string &str, const std::string &ends_with_str) {
size_t ends_len = ends_with_str.size();
return ends_len == 0 || (str.size() >= ends_len && memcmp(&str[str.size() - ends_len], ends_with_str.data(), ends_len) == 0);
diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp
index 24472e5..58bedf4 100644
--- a/src/plugins/MangaGeneric.cpp
+++ b/src/plugins/MangaGeneric.cpp
@@ -49,11 +49,6 @@ namespace QuickMedia {
return quickmedia_html_node_get_attribute_value(node, field_name);
}
- static bool starts_with(const std::string &str, const char *sub) {
- size_t sub_len = strlen(sub);
- return str.size() >= sub_len && memcmp(str.c_str(), sub, sub_len) == 0;
- }
-
static int html_append_search(QuickMediaHtmlSearch *html_search, const char *html_query, HtmlSearchUserdata *search_userdata) {
return quickmedia_html_find_nodes_xpath(html_search, html_query,
[](QuickMediaHtmlNode *node, void *userdata) {
@@ -149,12 +144,8 @@ namespace QuickMedia {
return plugin_result_to_search_result(get_page(str, 0, result_items));
}
- PluginResult MangaGenericSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
+ PluginResult MangaGenericSearchPage::get_page(const std::string &url, BodyItems &result_items) {
std::string target_url;
- std::string url = search_query.search_template;
- string_replace_all(url, "%s", url_param_encode(str));
- string_replace_all(url, "%p", std::to_string(search_query.page_start + page));
-
std::string website_data;
if(download_to_string(url, website_data, {}, true, fail_on_http_error) != DownloadResult::OK)
return PluginResult::NET_ERR;
@@ -184,36 +175,6 @@ namespace QuickMedia {
if(result != 0)
goto cleanup;
- for(const DescriptionQuery &description_query : description_queries) {
- assert(description_query.html_query && description_query.field_name);
- if(description_query.html_query && description_query.field_name) {
- HtmlMergeUserdata merge_userdata;
- merge_userdata.type = MergeType::DESCRIPTION;
- merge_userdata.body_item_image_context.body_items = &new_result_items;
- merge_userdata.body_item_image_context.index = 0;
- merge_userdata.field_name = description_query.field_name;
- merge_userdata.field_contains = nullptr;
- result = html_body_item_merge(&html_search, description_query.html_query, &merge_userdata);
- if(result != 0)
- goto cleanup;
- }
- }
-
- for(const ThumbnailQuery &thumbnail_query : thumbnail_queries) {
- assert(thumbnail_query.html_query && thumbnail_query.field_name);
- if(thumbnail_query.html_query && thumbnail_query.field_name) {
- HtmlMergeUserdata merge_userdata;
- merge_userdata.type = MergeType::THUMBNAIL;
- merge_userdata.body_item_image_context.body_items = &new_result_items;
- merge_userdata.body_item_image_context.index = 0;
- merge_userdata.field_name = thumbnail_query.field_name;
- merge_userdata.field_contains = thumbnail_query.field_contains;
- result = html_body_item_merge(&html_search, thumbnail_query.html_query, &merge_userdata);
- if(result != 0)
- goto cleanup;
- }
- }
-
if(!text_query.url_field && !new_result_items.empty()) {
if(target_url.empty()) {
std::string response_headers;
@@ -239,15 +200,45 @@ namespace QuickMedia {
result_items.insert(result_items.end(), std::move_iterator(new_result_items.begin()), std::move_iterator(new_result_items.end()));
}
+ for(const DescriptionQuery &description_query : description_queries) {
+ assert(description_query.html_query && description_query.field_name);
+ if(description_query.html_query && description_query.field_name) {
+ HtmlMergeUserdata merge_userdata;
+ merge_userdata.type = MergeType::DESCRIPTION;
+ merge_userdata.body_item_image_context.body_items = &result_items;
+ merge_userdata.body_item_image_context.index = 0;
+ merge_userdata.field_name = description_query.field_name;
+ merge_userdata.field_contains = nullptr;
+ result = html_body_item_merge(&html_search, description_query.html_query, &merge_userdata);
+ if(result != 0)
+ goto cleanup;
+ }
+ }
+
+ for(const ThumbnailQuery &thumbnail_query : thumbnail_queries) {
+ assert(thumbnail_query.html_query && thumbnail_query.field_name);
+ if(thumbnail_query.html_query && thumbnail_query.field_name) {
+ HtmlMergeUserdata merge_userdata;
+ merge_userdata.type = MergeType::THUMBNAIL;
+ merge_userdata.body_item_image_context.body_items = &result_items;
+ merge_userdata.body_item_image_context.index = 0;
+ merge_userdata.field_name = thumbnail_query.field_name;
+ merge_userdata.field_contains = thumbnail_query.field_contains;
+ result = html_body_item_merge(&html_search, thumbnail_query.html_query, &merge_userdata);
+ if(result != 0)
+ goto cleanup;
+ }
+ }
+
for(auto &body_item : result_items) {
- if(starts_with(body_item->url, "//"))
+ if(string_starts_with(body_item->url, "//"))
body_item->url = "https://" + body_item->url.substr(2);
- else if(starts_with(body_item->url, "/"))
+ else if(string_starts_with(body_item->url, "/"))
body_item->url = website_url + body_item->url.substr(1);
- if(starts_with(body_item->thumbnail_url, "//"))
+ if(string_starts_with(body_item->thumbnail_url, "//"))
body_item->thumbnail_url = "https://" + body_item->thumbnail_url.substr(2);
- else if(starts_with(body_item->thumbnail_url, "/"))
+ else if(string_starts_with(body_item->thumbnail_url, "/"))
body_item->thumbnail_url = website_url + body_item->thumbnail_url.substr(1);
}
@@ -261,12 +252,20 @@ namespace QuickMedia {
}
}
+ PluginResult MangaGenericSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
+ std::string url = search_query.search_template;
+ string_replace_all(url, "%s", url_param_encode(str));
+ string_replace_all(url, "%p", std::to_string(search_query.page_start + page));
+ return get_page(url, result_items);
+ }
+
PluginResult MangaGenericSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
if(!list_chapters_query.html_query || !list_chapters_query.title_field || !list_chapters_query.url_field) {
assert(false);
return PluginResult::ERR;
}
+ std::vector<Creator> creators;
BodyItems chapters_items;
HtmlSearchUserdata search_userdata;
search_userdata.body_items = &chapters_items;
@@ -299,18 +298,49 @@ namespace QuickMedia {
result = html_body_item_merge(&html_search, list_chapters_query.uploaded_time_html_query, &merge_userdata);
}
+ if(authors_query.html_query && authors_query.title_field && authors_query.url_field) {
+ struct HtmlAuthorsUserdata {
+ std::vector<Creator> *creators;
+ AuthorsQuery *authors_query;
+ };
+
+ HtmlAuthorsUserdata authors_userdata;
+ authors_userdata.creators = &creators;
+ authors_userdata.authors_query = &authors_query;
+
+ quickmedia_html_find_nodes_xpath(&html_search, authors_query.html_query,
+ [](QuickMediaHtmlNode *node, void *userdata) {
+ HtmlAuthorsUserdata *authors_userdata = (HtmlAuthorsUserdata*)userdata;
+ const char *title_value = html_attr_or_inner_text(node, authors_userdata->authors_query->title_field);
+ const char *url_value = html_attr_or_inner_text(node, authors_userdata->authors_query->url_field);
+ if(title_value && url_value && (!authors_userdata->authors_query->url_contains || strstr(url_value, authors_userdata->authors_query->url_contains))) {
+ Creator creator;
+ creator.name = strip(title_value);
+ creator.url = strip(url_value);
+ authors_userdata->creators->push_back(std::move(creator));
+ }
+ }, &authors_userdata);
+ }
+
for(auto &body_item : chapters_items) {
- if(starts_with(body_item->url, "//"))
+ if(string_starts_with(body_item->url, "//"))
body_item->url = "https://" + body_item->url.substr(2);
- else if(starts_with(body_item->url, "/"))
+ else if(string_starts_with(body_item->url, "/"))
body_item->url = website_url + body_item->url.substr(1);
- if(starts_with(body_item->thumbnail_url, "//"))
+ if(string_starts_with(body_item->thumbnail_url, "//"))
body_item->thumbnail_url = "https://" + body_item->thumbnail_url.substr(2);
- else if(starts_with(body_item->thumbnail_url, "/"))
+ else if(string_starts_with(body_item->thumbnail_url, "/"))
body_item->thumbnail_url = website_url + body_item->thumbnail_url.substr(1);
}
+ for(auto &creator : creators) {
+ if(string_starts_with(creator.url, "//"))
+ creator.url = "https://" + creator.url.substr(2);
+ else if(string_starts_with(creator.url, "/"))
+ creator.url = website_url + creator.url.substr(1);
+ }
+
cleanup:
quickmedia_html_search_deinit(&html_search);
if(result != 0)
@@ -319,6 +349,11 @@ namespace QuickMedia {
auto body = create_body();
body->items = std::move(chapters_items);
result_tabs.push_back(Tab{std::move(body), std::make_unique<MangaGenericChaptersPage>(program, title, url, manga_id_extractor, service_name, website_url, &list_page_query, fail_on_http_error), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ for(Creator &creator : creators) {
+ result_tabs.push_back(Tab{create_body(), std::make_unique<MangaGenericCreatorPage>(program, this, std::move(creator)), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ }
+
return PluginResult::OK;
}
@@ -348,6 +383,14 @@ namespace QuickMedia {
return true;
}
+ PluginResult MangaGenericCreatorPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ return search_page->submit(title, url, result_tabs);
+ }
+
+ PluginResult MangaGenericCreatorPage::lazy_fetch(BodyItems &result_items) {
+ return search_page->get_page(creator.url, result_items);
+ }
+
static bool is_number(const char *str) {
while(*str) {
char c = *str;
@@ -443,9 +486,9 @@ namespace QuickMedia {
return ImageResult::ERR;
}
- if(starts_with(current_image_url, "//"))
+ if(string_starts_with(current_image_url, "//"))
current_image_url = "https://" + current_image_url.substr(2);
- else if(starts_with(current_image_url, "/"))
+ else if(string_starts_with(current_image_url, "/"))
current_image_url = website_url + current_image_url.substr(1);
num_images = page_count_userdata.num_pages;
@@ -523,9 +566,9 @@ namespace QuickMedia {
cleanup:
quickmedia_html_search_deinit(&html_search);
- if(starts_with(current_image_url, "//"))
+ if(string_starts_with(current_image_url, "//"))
current_image_url = "https://" + current_image_url.substr(2);
- else if(starts_with(current_image_url, "/"))
+ else if(string_starts_with(current_image_url, "/"))
current_image_url = website_url + current_image_url.substr(1);
if(!callback(current_image_url))
@@ -603,9 +646,9 @@ namespace QuickMedia {
}
for(std::string &url : chapter_image_urls) {
- if(starts_with(url, "//"))
+ if(string_starts_with(url, "//"))
url = "https://" + url.substr(2);
- else if(starts_with(url, "/"))
+ else if(string_starts_with(url, "/"))
url = website_url + url.substr(1);
}
@@ -635,6 +678,14 @@ namespace QuickMedia {
return *this;
}
+ MangaGenericSearchPage& MangaGenericSearchPage::authors_handler(const char *html_query, const char *title_field, const char *url_field, const char *url_contains) {
+ authors_query.html_query = html_query;
+ authors_query.title_field = title_field;
+ authors_query.url_field = url_field;
+ authors_query.url_contains = url_contains;
+ return *this;
+ }
+
MangaGenericSearchPage& MangaGenericSearchPage::list_chapters_handler(const char *html_query, const char *title_field, const char *url_field, const char *url_contains) {
list_chapters_query.html_query = html_query;
list_chapters_query.title_field = title_field;
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 57c879e..0c06458 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -489,14 +489,15 @@ namespace QuickMedia {
}
static std::string message_to_room_description_text(Message *message) {
+ std::string body = strip(message->body);
if(message->type == MessageType::REACTION)
- return "Reacted with: " + extract_first_line_elipses(message->body, 150);
+ return "Reacted with: " + extract_first_line_elipses(body, 150);
else if(message->related_event_type == RelatedEventType::REPLY)
- return extract_first_line_elipses(remove_reply_formatting(message->body), 150);
+ return extract_first_line_elipses(remove_reply_formatting(body), 150);
else if(message->related_event_type == RelatedEventType::EDIT)
- return "Edited: " + extract_first_line_elipses(remove_reply_formatting(message->body), 150);
+ return "Edited: " + extract_first_line_elipses(remove_reply_formatting(body), 150);
else
- return extract_first_line_elipses(message->body, 150);
+ return extract_first_line_elipses(body, 150);
}
void MatrixQuickMedia::update_room_description(RoomData *room, Messages &new_messages, bool is_initial_sync, bool sync_is_cache, Body *chat_body, bool messages_tab_visible) {
@@ -1090,18 +1091,20 @@ namespace QuickMedia {
}
}
- void Matrix::start_sync(MatrixDelegate *delegate, bool &cached) {
+ bool Matrix::start_sync(MatrixDelegate *delegate, bool &cached) {
cached = true;
if(sync_running)
- return;
+ return true;
assert(!this->delegate);
assert(!access_token.empty()); // Need to be logged in
this->delegate = delegate;
Path matrix_cache_dir = get_cache_dir().join("matrix");
- if(create_directory_recursive(matrix_cache_dir) != 0)
+ if(create_directory_recursive(matrix_cache_dir) != 0) {
fprintf(stderr, "Failed to create matrix cache directory\n");
+ return false;
+ }
matrix_cache_dir.join("sync_data.json");
cached = (get_file_type(matrix_cache_dir) == FileType::REGULAR);
@@ -1196,6 +1199,7 @@ namespace QuickMedia {
result = parse_sync_response(json_root, false, initial_sync);
if(result != PluginResult::OK) {
fprintf(stderr, "Failed to parse sync response\n");
+ initial_sync = false;
goto sync_end;
}
@@ -1206,6 +1210,7 @@ namespace QuickMedia {
} else {
//set_next_batch("Invalid");
fprintf(stderr, "Matrix: missing next batch\n");
+ initial_sync = false;
goto sync_end;
}
@@ -1288,6 +1293,8 @@ namespace QuickMedia {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
});
+
+ return true;
}
void Matrix::stop_sync() {
@@ -1313,6 +1320,10 @@ namespace QuickMedia {
delegate = nullptr;
sync_failed = false;
sync_fail_reason.clear();
+ set_next_batch("");
+ invites.clear();
+ filter_cached.reset();
+ my_events_transaction_ids.clear();
}
bool Matrix::is_initial_sync_finished() const {
@@ -3382,7 +3393,7 @@ namespace QuickMedia {
}
PluginResult Matrix::logout() {
- assert(!sync_running);
+ stop_sync();
Path session_path = get_storage_dir().join(SERVICE_NAME).join("session.json");
remove(session_path.data.c_str());
@@ -3402,10 +3413,6 @@ namespace QuickMedia {
homeserver.clear();
homeserver_domain.clear();
upload_limit.reset();
- set_next_batch("");
- invites.clear();
- filter_cached.reset();
- my_events_transaction_ids.clear();
return PluginResult::OK;
}
@@ -3950,7 +3957,7 @@ namespace QuickMedia {
// TODO: Use at param? which is room->get_prev_batch();
char url[512];
- snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/members?membership=join", homeserver.c_str(), room->id.c_str());
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/members?not_membership=leave", homeserver.c_str(), room->id.c_str());
rapidjson::Document json_root;
DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true);
diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp
index a0184b4..6740504 100644
--- a/src/plugins/Soundcloud.cpp
+++ b/src/plugins/Soundcloud.cpp
@@ -2,9 +2,11 @@
#include "../../include/NetUtils.hpp"
#include "../../include/StringUtils.hpp"
#include "../../include/Scale.hpp"
+#include <quickmedia/HtmlSearch.h>
+#include <thread>
namespace QuickMedia {
- static std::string client_id = "Na04L87fnpWDMVCCW2ngWldN4JMoLTAc";
+ static std::string client_id;
class SoundcloudPlaylist : public BodyItemExtra {
public:
@@ -218,6 +220,11 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ PluginResult SoundcloudSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ submit_page.submit_body_item = submit_body_item;
+ return submit_page.submit(title, url, result_tabs);
+ }
+
SearchResult SoundcloudSearchPage::search(const std::string &str, BodyItems &result_items) {
query_urn.clear();
PluginResult result = get_page(str, 0, result_items);
@@ -268,6 +275,65 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ PluginResult SoundcloudSearchPage::lazy_fetch(BodyItems&) {
+ std::string website_data;
+ DownloadResult download_result = download_to_string("https://soundcloud.com/", website_data, {}, true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ if(client_id.empty()) {
+ std::vector<std::string> script_sources;
+ 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, "//script",
+ [](QuickMediaHtmlNode *node, void *userdata) {
+ std::vector<std::string> *script_sources = (std::vector<std::string>*)userdata;
+ const char *src = quickmedia_html_node_get_attribute_value(node, "src");
+ if(src)
+ script_sources->push_back(strip(src));
+ }, &script_sources);
+
+ cleanup:
+ quickmedia_html_search_deinit(&html_search);
+ if(result != 0)
+ return PluginResult::ERR;
+
+ std::vector<std::future<std::string>> async_download_threads;
+ for(std::string &script_source : script_sources) {
+ if(string_starts_with(script_source, "//"))
+ script_source = "https://" + script_source.substr(2);
+ else if(string_starts_with(script_source, "/"))
+ script_source = "https://soundcloud.com/" + script_source.substr(1);
+
+ async_download_threads.push_back(std::async(std::launch::async, [script_source]() -> std::string {
+ std::string website_data;
+ DownloadResult download_result = download_to_string(script_source, website_data, {}, true);
+ if(download_result != DownloadResult::OK) return "";
+
+ size_t index = website_data.find("web-auth?client_id=");
+ if(index == std::string::npos) return "";
+
+ index += 19;
+ size_t end = website_data.find('&', index);
+ if(end == std::string::npos) end = website_data.size();
+ return website_data.substr(index, end - index);
+ }));
+ }
+
+ for(auto &download_thread : async_download_threads) {
+ if(download_thread.valid()) {
+ std::string fetched_client_id = download_thread.get();
+ if(client_id.empty() && !fetched_client_id.empty())
+ client_id = std::move(fetched_client_id);
+ }
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
PluginResult SoundcloudUserPage::get_page(const std::string&, int page, BodyItems &result_items) {
while(current_page < page) {
PluginResult plugin_result = get_continuation_page(result_items);