From e1a8d10b61c5f8ca092ba3aa458b661da29ba447 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 30 Sep 2020 19:07:05 +0200 Subject: Matrix: add message replying with ctrl+r, also use shared_ptr for BodyItem --- src/Body.cpp | 36 ++++++++-- src/QuickMedia.cpp | 108 ++++++++++++++++++++++------ src/plugins/Dmenu.cpp | 2 +- src/plugins/FileManager.cpp | 2 +- src/plugins/Fourchan.cpp | 10 +-- src/plugins/Mangadex.cpp | 4 +- src/plugins/Manganelo.cpp | 6 +- src/plugins/Mangatown.cpp | 10 +-- src/plugins/Matrix.cpp | 172 ++++++++++++++++++++++++++++++++++++-------- src/plugins/NyaaSi.cpp | 14 ++-- src/plugins/Pornhub.cpp | 4 +- src/plugins/Youtube.cpp | 14 ++-- 12 files changed, 288 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index 718baee..5927831 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -24,7 +24,8 @@ namespace QuickMedia { dirty_author(false), thumbnail_is_local(false), title_color(sf::Color::White), - author_color(sf::Color::White) + author_color(sf::Color::White), + userdata(nullptr) { if(!_title.empty()) set_title(std::move(_title)); @@ -58,6 +59,7 @@ namespace QuickMedia { post_number = other.post_number; title_color = other.title_color; author_color = other.author_color; + userdata = other.userdata; } Body::Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font) : @@ -233,6 +235,12 @@ namespace QuickMedia { return items[selected_item].get(); } + std::shared_ptr Body::get_selected_shared() { + if(selected_item < 0 || selected_item >= (int)items.size() || !items[selected_item]->visible) + return nullptr; + return items[selected_item]; + } + void Body::clamp_selection() { int num_items = (int)items.size(); if(items.empty()) @@ -373,8 +381,7 @@ namespace QuickMedia { } void Body::draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size) { - Json::Value empty_object(Json::objectValue); - draw(window, pos, size, empty_object); + draw(window, pos, size, Json::nullValue); } // TODO: Use a render target for the whole body so all images can be put into one. @@ -502,9 +509,9 @@ namespace QuickMedia { continue; float item_height = get_item_height(item.get()); - prev_pos.y -= item_height + spacing_y; + prev_pos.y -= (item_height + spacing_y); - if(prev_pos.y + item_height <= 0.0f) + if(prev_pos.y + item_height + spacing_y < start_y) break; draw_item(window, item.get(), prev_pos, size, item_height, i, content_progress); @@ -521,10 +528,10 @@ namespace QuickMedia { float item_height = get_item_height(item.get()); - if(after_pos.y + item_height > start_y + size.y) + if((after_pos.y - start_y) + item_height + spacing_y > size.y) last_item_fully_visible = false; - if(after_pos.y >= start_y + size.y) + if(after_pos.y - start_y >= size.y) break; draw_item(window, item.get(), after_pos, size, item_height, i, content_progress); @@ -542,6 +549,14 @@ namespace QuickMedia { } } + void Body::draw_item(sf::RenderWindow &window, BodyItem *item, sf::Vector2f pos, sf::Vector2f size) { + //sf::Vector2u window_size = window.getSize(); + // glEnable(GL_SCISSOR_TEST); + //glScissor(pos.x, (int)window_size.y - (int)pos.y - (int)pos.y, size.x, size.y); + draw_item(window, item, pos, size, get_item_height(item) + spacing_y, -1, Json::nullValue); + //glDisable(GL_SCISSOR_TEST); + } + void Body::draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress) { // TODO: Instead of generating a new hash everytime to access textures, cache the hash of the thumbnail url std::shared_ptr item_thumbnail; @@ -646,6 +661,9 @@ namespace QuickMedia { item->description_text->draw(window); } + if(!content_progress.isObject()) + return; + // TODO: Do the same for non-manga content. // TODO: Cache this instead of hash access every item every frame. const Json::Value &item_progress = content_progress[item->get_title()]; @@ -692,6 +710,10 @@ namespace QuickMedia { return item_height + padding_y * 2.0f; } + float Body::get_spacing_y() const { + return spacing_y; + } + //static bool Body::string_find_case_insensitive(const std::string &str, const std::string &substr) { auto it = std::search(str.begin(), str.end(), substr.begin(), substr.end(), diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 8a7cbf6..c3e61fe 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -678,7 +678,7 @@ namespace QuickMedia { if(!timestamp.isNumeric()) continue; - auto body_item = std::make_unique(std::move(title_str)); + auto body_item = BodyItem::create(std::move(title_str)); body_item->url = "https://www.youtube.com/watch?v=" + video_id_str; body_item->thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg"; body_item->set_description(timestamp_to_relative_time_str(std::max(0l, time_now - timestamp.asInt64()))); @@ -746,7 +746,7 @@ namespace QuickMedia { if(!recommended_title_json.isString()) continue; - auto body_item = std::make_unique(recommended_title_json.asString()); + auto body_item = BodyItem::create(recommended_title_json.asString()); body_item->url = "https://www.youtube.com/watch?v=" + recommended_item_id; body_item->thumbnail_url = "https://img.youtube.com/vi/" + recommended_item_id + "/hqdefault.jpg"; body_items.push_back(std::move(body_item)); @@ -837,7 +837,7 @@ namespace QuickMedia { const Json::Value &manga_name = body["name"]; if(!filename.empty() && manga_name.isString()) { // TODO: Add thumbnail - auto body_item = std::make_unique(manga_name.asString()); + auto body_item = BodyItem::create(manga_name.asString()); if(plugin->name == "manganelo") body_item->url = "https://manganelo.com/manga/" + base64_decode(filename.string()); else if(plugin->name == "mangadex") @@ -3343,14 +3343,23 @@ namespace QuickMedia { // get_all_room_messages is not needed here because its done in the loop, where the initial timeout is 0ms + enum class ChatState { + NAVIGATING, + TYPING_MESSAGE, + REPLYING + }; + Page new_page = Page::CHAT; - bool typing_message = false; + ChatState chat_state = ChatState::NAVIGATING; + + std::shared_ptr replying_to_message; + sf::Text replying_to_text("Replying to:", *font, 18); sf::Sprite logo_sprite(plugin_logo); Entry chat_input("Press ctrl+m to begin writing a message...", font.get(), cjk_font.get()); chat_input.set_editable(false); - chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, ¤t_room_id, &new_page, &typing_message](const sf::String &text) { + chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, ¤t_room_id, &new_page, &chat_state, &replying_to_message](const sf::String &text) mutable { if(tabs[selected_tab].type == ChatTabType::MESSAGES) { if(text.isEmpty()) return false; @@ -3361,12 +3370,12 @@ namespace QuickMedia { if(command == "/upload") { new_page = Page::FILE_MANAGER; chat_input.set_editable(false); - typing_message = false; + chat_state = ChatState::NAVIGATING; return true; } else if(command == "/logout") { new_page = Page::CHAT_LOGIN; chat_input.set_editable(false); - typing_message = false; + chat_state = ChatState::NAVIGATING; return true; } else { fprintf(stderr, "Error: invalid command: %s, expected /upload\n", command.c_str()); @@ -3374,14 +3383,29 @@ namespace QuickMedia { } } - // TODO: Make asynchronous - if(matrix->post_message(current_room_id, text) == PluginResult::OK) { - chat_input.set_editable(false); - typing_message = false; - return true; - } else { - show_notification("QuickMedia", "Failed to post matrix message", Urgency::CRITICAL); - return false; + tabs[selected_tab].body->select_last_item(); + + if(chat_state == ChatState::TYPING_MESSAGE) { + // TODO: Make asynchronous + if(matrix->post_message(current_room_id, text) == PluginResult::OK) { + chat_input.set_editable(false); + chat_state = ChatState::NAVIGATING; + return true; + } else { + show_notification("QuickMedia", "Failed to post matrix message", Urgency::CRITICAL); + return false; + } + } else if(chat_state == ChatState::REPLYING) { + // TODO: Make asynchronous + if(matrix->post_reply(current_room_id, text, replying_to_message->userdata) == PluginResult::OK) { + chat_input.set_editable(false); + chat_state = ChatState::NAVIGATING; + replying_to_message = nullptr; + return true; + } else { + show_notification("QuickMedia", "Failed to post matrix reply", Urgency::CRITICAL); + return false; + } } } return false; @@ -3437,6 +3461,7 @@ namespace QuickMedia { sf::Clock frame_timer; float prev_chat_height = chat_input.get_height(); + float chat_input_height_full = 0.0f; while (current_page == Page::CHAT) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); @@ -3444,7 +3469,7 @@ namespace QuickMedia { base_event_handler(event, Page::EXIT, false, false, false); if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) { redraw = true; - } else if(event.type == sf::Event::KeyPressed && !typing_message) { + } else if(event.type == sf::Event::KeyPressed && chat_state == ChatState::NAVIGATING) { if(event.key.code == sf::Keyboard::Up || event.key.code == sf::Keyboard::PageUp || event.key.code == sf::Keyboard::Home) { bool hit_top = false; switch(event.key.code) { @@ -3516,15 +3541,25 @@ namespace QuickMedia { current_page = Page::VIDEO_CONTENT; video_content_page(); redraw = true; + } else if(tabs[selected_tab].type == ChatTabType::MESSAGES && event.key.control && event.key.code == sf::Keyboard::R) { + std::shared_ptr selected = tabs[selected_tab].body->get_selected_shared(); + if(selected) { + chat_state = ChatState::REPLYING; + replying_to_message = selected; + chat_input.set_editable(true); + } else { + // TODO: Show inline notification + show_notification("QuickMedia", "No message selected for replying"); + } } } - if(!typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::M && event.key.control) { + if(chat_state == ChatState::NAVIGATING && tabs[selected_tab].type == ChatTabType::MESSAGES && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::M && event.key.control) { chat_input.set_editable(true); - typing_message = true; + chat_state = ChatState::TYPING_MESSAGE; } - if(typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES) { + if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING) && tabs[selected_tab].type == ChatTabType::MESSAGES) { if(event.type == sf::Event::TextEntered) { //chat_input.onTextEntered(event.text.unicode); // TODO: Also show typing event when ctrl+v pasting? @@ -3538,7 +3573,8 @@ namespace QuickMedia { } } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) { chat_input.set_editable(false); - typing_message = false; + chat_state = ChatState::NAVIGATING; + replying_to_message = nullptr; } //chat_input.on_event(event); chat_input.process_event(event); @@ -3649,7 +3685,8 @@ namespace QuickMedia { body_padding_horizontal = 0.0f; } - chat_input_shade.setSize(sf::Vector2f(window_size.x, chat_input.get_height() + chat_input_padding_y * 2.0f)); + chat_input_height_full = chat_input.get_height() + chat_input_padding_y * 2.0f; + chat_input_shade.setSize(sf::Vector2f(window_size.x, chat_input_height_full)); chat_input_shade.setPosition(0.0f, window_size.y - chat_input_shade.getSize().y); body_pos = sf::Vector2f(body_padding_horizontal, body_padding_vertical + tab_shade_height); @@ -3675,7 +3712,7 @@ namespace QuickMedia { BodyItems result_items; if(matrix->sync() == PluginResult::OK) { fprintf(stderr, "Synced matrix\n"); - if(matrix->get_new_room_messages(sync_future_room_id, result_items) == PluginResult::OK) { + if(matrix->get_new_room_messages(sync_future_room_id, result_items) != PluginResult::OK) { fprintf(stderr, "Failed to get new matrix messages in room: %s\n", sync_future_room_id.c_str()); } } else { @@ -3691,7 +3728,7 @@ namespace QuickMedia { // Ignore finished sync if it happened in another room. When we navigate back to the room we will get the messages again if(sync_future_room_id == current_room_id) { int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); - bool scroll_to_end = (num_items > 0 && tabs[MESSAGES_TAB_INDEX].body->get_selected_item() == num_items - 1); + bool scroll_to_end = (num_items == 0 || (num_items > 0 && tabs[MESSAGES_TAB_INDEX].body->get_selected_item() == num_items - 1)); tabs[MESSAGES_TAB_INDEX].body->append_items(std::move(new_body_items)); if(scroll_to_end) tabs[MESSAGES_TAB_INDEX].body->select_last_item(); @@ -3762,6 +3799,31 @@ namespace QuickMedia { window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl } + if(chat_state == ChatState::REPLYING) { + const float margin = 5.0f; + const float replying_to_text_height = replying_to_text.getLocalBounds().height + margin; + + const float item_height = std::min(body_size.y - replying_to_text_height - margin, tabs[MESSAGES_TAB_INDEX].body->get_item_height(replying_to_message.get()) + margin); + + sf::RectangleShape overlay(sf::Vector2f(window_size.x, window_size.y - tab_shade_height - chat_input_height_full)); + overlay.setPosition(0.0f, tab_shade_height); + overlay.setFillColor(sf::Color(0, 0, 0, 240)); + window.draw(overlay); + + sf::Vector2f body_item_pos(body_pos.x, window_size.y - chat_input_height_full - item_height); + sf::Vector2f body_item_size(body_size.x, item_height); + + sf::RectangleShape item_background(sf::Vector2f(window_size.x, body_item_size.y + replying_to_text_height + margin)); + item_background.setPosition(sf::Vector2f(0.0f, body_item_pos.y - replying_to_text_height - margin)); + item_background.setFillColor(back_color); + window.draw(item_background); + + replying_to_text.setPosition(body_item_pos.x, body_item_pos.y - replying_to_text_height); + window.draw(replying_to_text); + + tabs[MESSAGES_TAB_INDEX].body->draw_item(window, replying_to_message.get(), body_item_pos, body_item_size); + } + if(tabs[selected_tab].type == ChatTabType::MESSAGES && !tabs[selected_tab].body->is_last_item_fully_visible()) { window.draw(more_messages_below_rect); } diff --git a/src/plugins/Dmenu.cpp b/src/plugins/Dmenu.cpp index 5c46841..9a8b5b8 100644 --- a/src/plugins/Dmenu.cpp +++ b/src/plugins/Dmenu.cpp @@ -11,7 +11,7 @@ namespace QuickMedia { PluginResult Dmenu::get_front_page(BodyItems &result_items) { for(const std::string &line_data : stdin_data) { - result_items.push_back(std::make_unique(line_data)); + result_items.push_back(BodyItem::create(line_data)); } return PluginResult::OK; } diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp index c68bb94..d8a0612 100644 --- a/src/plugins/FileManager.cpp +++ b/src/plugins/FileManager.cpp @@ -42,7 +42,7 @@ namespace QuickMedia { }); for(auto &p : paths) { - auto body_item = std::make_unique(p.path().filename().string()); + auto body_item = BodyItem::create(p.path().filename().string()); // TODO: Check file magic number instead of extension? if(p.is_regular_file() && is_image_ext(get_ext(p.path()))) { body_item->thumbnail_is_local = true; diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp index 5da2961..fa5e9e3 100644 --- a/src/plugins/Fourchan.cpp +++ b/src/plugins/Fourchan.cpp @@ -105,7 +105,7 @@ namespace QuickMedia { 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 = std::make_unique("/" + board_id.asString() + "/ " + board_title.asString()); + 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)); } @@ -320,7 +320,7 @@ namespace QuickMedia { } } } - auto body_item = std::make_unique(std::move(comment_text)); + auto body_item = BodyItem::create(std::move(comment_text)); body_item->url = std::to_string(thread_num.asInt64()); const Json::Value &ext = thread["ext"]; @@ -382,7 +382,7 @@ namespace QuickMedia { std::lock_guard lock(board_list_mutex); BodyItems body_items; for(auto &cached_body_item : cached_thread_list_items) { - body_items.push_back(std::make_unique(*cached_body_item)); + body_items.push_back(std::make_shared(*cached_body_item)); } return body_items; } @@ -432,7 +432,7 @@ namespace QuickMedia { int64_t post_num_int = post_num.asInt64(); comment_by_postno[post_num_int] = result_items.size(); - result_items.push_back(std::make_unique("")); + result_items.push_back(BodyItem::create("")); result_items.back()->post_number = std::to_string(post_num_int); } } @@ -602,7 +602,7 @@ namespace QuickMedia { ++it; for(; it != cached_media_urls.end(); ++it) { - auto body_item = std::make_unique(""); + auto body_item = BodyItem::create(""); body_item->url = *it; body_items.push_back(std::move(body_item)); } diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp index fcbd23e..e9487bc 100644 --- a/src/plugins/Mangadex.cpp +++ b/src/plugins/Mangadex.cpp @@ -115,7 +115,7 @@ namespace QuickMedia { if(chapter_title_json.isString() && strlen(chapter_title_json.asCString()) > 0) chapter_name += std::string(" - ") + chapter_title_json.asCString(); - auto item = std::make_unique(std::move(chapter_name)); + auto item = BodyItem::create(std::move(chapter_name)); item->url = std::move(chapter_url); result_items.push_back(std::move(item)); } @@ -194,7 +194,7 @@ namespace QuickMedia { 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 = std::make_unique(strip(title)); + auto item = BodyItem::create(strip(title)); item->url = mangadex_url + href; item_data->push_back(std::move(item)); } diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index 6363991..5be64ca 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -27,7 +27,7 @@ namespace QuickMedia { 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 = std::make_unique(strip(text)); + auto item = BodyItem::create(strip(text)); item->url = href; item_data->push_back(std::move(item)); } @@ -101,7 +101,7 @@ namespace QuickMedia { 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 = std::make_unique(strip(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') @@ -228,7 +228,7 @@ namespace QuickMedia { 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 = std::make_unique(title); + auto body_item = BodyItem::create(title); body_item->url = href; item_data->push_back(std::move(body_item)); } diff --git a/src/plugins/Mangatown.cpp b/src/plugins/Mangatown.cpp index 67f4c29..0964310 100644 --- a/src/plugins/Mangatown.cpp +++ b/src/plugins/Mangatown.cpp @@ -22,7 +22,7 @@ namespace QuickMedia { 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 = std::make_unique(strip(text)); + auto item = BodyItem::create(strip(text)); item->url = mangatown_url + href; item_data->push_back(std::move(item)); } @@ -76,10 +76,10 @@ namespace QuickMedia { for(const Json::Value &child : json_suggestions) { if(!child.isString()) { - result_items.push_back(std::make_unique("")); + result_items.push_back(BodyItem::create("")); continue; } - result_items.push_back(std::make_unique(child.asString())); + result_items.push_back(BodyItem::create(child.asString())); } size_t index = 0; @@ -88,7 +88,7 @@ namespace QuickMedia { if(index < result_items.size()) { body_item = result_items[index].get(); } else { - result_items.push_back(std::make_unique("")); + result_items.push_back(BodyItem::create("")); body_item = result_items.back().get(); } @@ -123,7 +123,7 @@ namespace QuickMedia { 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 && strncmp(href, "/manga/", 7) == 0) { - auto item = std::make_unique(strip(title)); + auto item = BodyItem::create(strip(title)); item->url = mangatown_url + href; item_data->push_back(std::move(item)); } diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 513a9fb..50be2de 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -150,7 +150,7 @@ namespace QuickMedia { avatar_url = room_it->second->avatar_url; } - auto body_item = std::make_unique(std::move(room_name)); + auto body_item = BodyItem::create(std::move(room_name)); body_item->url = room_id_str; body_item->thumbnail_url = std::move(avatar_url); result_items.push_back(std::move(body_item)); @@ -159,21 +159,22 @@ namespace QuickMedia { return PluginResult::OK; } - static void room_messages_to_body_items(RoomData *room_data, Message *messages, size_t num_messages, BodyItems &result_items) { + static void room_messages_to_body_items(RoomData *room_data, std::shared_ptr *messages, size_t num_messages, BodyItems &result_items) { for(size_t i = 0; i < num_messages; ++i) { - const UserInfo &user_info = room_data->user_info[messages[i].user_id]; - auto body_item = std::make_unique(""); + const UserInfo &user_info = room_data->user_info[messages[i]->user_id]; + auto body_item = BodyItem::create(""); body_item->set_author(user_info.display_name); - body_item->set_description(messages[i].body); - if(!messages[i].thumbnail_url.empty()) - body_item->thumbnail_url = messages[i].thumbnail_url; - else if(!messages[i].url.empty() && messages->type == MessageType::IMAGE) - body_item->thumbnail_url = messages[i].url; + body_item->set_description(messages[i]->body); + if(!messages[i]->thumbnail_url.empty()) + body_item->thumbnail_url = messages[i]->thumbnail_url; + else if(!messages[i]->url.empty() && messages[i]->type == MessageType::IMAGE) + body_item->thumbnail_url = messages[i]->url; else body_item->thumbnail_url = user_info.avatar_url; // TODO: Show image thumbnail inline instead of url to image and showing it as the thumbnail of the body item - body_item->url = messages[i].url; + body_item->url = messages[i]->url; body_item->author_color = user_info.display_name_color; + body_item->userdata = (void*)messages[i].get(); // Note: messages[i] has to be valid as long as body_item is used! result_items.push_back(std::move(body_item)); } } @@ -368,6 +369,7 @@ namespace QuickMedia { continue; UserInfo user_info; + user_info.user_id = sender_json_str; user_info.avatar_url = avatar_url_json.asString(); if(strncmp(user_info.avatar_url.c_str(), "mxc://", 6) == 0) user_info.avatar_url.erase(user_info.avatar_url.begin(), user_info.avatar_url.begin() + 6); @@ -375,7 +377,7 @@ namespace QuickMedia { user_info.avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + user_info.avatar_url + "?width=32&height=32&method=crop"; user_info.display_name = display_name_json.asString(); user_info.display_name_color = user_id_to_color(sender_json_str); - room_data->user_info.push_back(std::move(user_info)); + room_data->user_info.push_back(user_info); room_data->user_info_by_user_id.insert(std::make_pair(sender_json_str, room_data->user_info.size() - 1)); } } @@ -399,7 +401,7 @@ namespace QuickMedia { if(!events_json.isArray()) return; - std::vector new_messages; + std::vector> new_messages; for(const Json::Value &event_item_json : events_json) { if(!event_item_json.isObject()) @@ -414,6 +416,12 @@ namespace QuickMedia { continue; std::string sender_json_str = sender_json.asString(); + + const Json::Value &event_id_json = event_item_json["event_id"]; + if(!event_id_json.isString()) + continue; + + std::string event_id_str = event_id_json.asString(); const Json::Value &content_json = event_item_json["content"]; if(!content_json.isObject()) @@ -425,6 +433,7 @@ namespace QuickMedia { auto user_it = room_data->user_info_by_user_id.find(sender_json_str); if(user_it == room_data->user_info_by_user_id.end()) { + // Note: this is important because otherwise replying and such is broken fprintf(stderr, "Warning: skipping unknown user: %s\n", sender_json_str.c_str()); continue; } @@ -433,36 +442,52 @@ namespace QuickMedia { if(!body_json.isString()) continue; + bool is_reply = false; + const Json::Value &relates_to_json = content_json["m.relates_to"]; + if(relates_to_json.isObject()) { + const Json::Value &in_reply_to_json = relates_to_json["m.in_reply_to"]; + is_reply = in_reply_to_json.isObject(); + } + if(strcmp(content_type.asCString(), "m.text") == 0) { - Message message; - message.user_id = user_it->second; - message.body = body_json.asString(); - message.type = MessageType::TEXT; - new_messages.push_back(std::move(message)); + auto message = std::make_shared(); + message->user_id = user_it->second; + message->event_id = event_id_str; + message->body = body_json.asString(); + message->type = MessageType::TEXT; + message->is_reply = is_reply; + new_messages.push_back(message); + room_data->message_by_event_id[event_id_str] = message; } else if(strcmp(content_type.asCString(), "m.image") == 0) { const Json::Value &url_json = content_json["url"]; if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0) continue; - Message message; - message.user_id = user_it->second; - message.body = body_json.asString(); - message.url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); - message.thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); - message.type = MessageType::IMAGE; - new_messages.push_back(std::move(message)); + auto message = std::make_shared(); + message->user_id = user_it->second; + message->event_id = event_id_str; + message->body = body_json.asString(); + message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); + message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); + message->type = MessageType::IMAGE; + message->is_reply = is_reply; + new_messages.push_back(message); + room_data->message_by_event_id[event_id_str] = message; } else if(strcmp(content_type.asCString(), "m.video") == 0) { const Json::Value &url_json = content_json["url"]; if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0) continue; - Message message; - message.user_id = user_it->second; - message.body = body_json.asString(); - message.url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); - message.thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); - message.type = MessageType::VIDEO; - new_messages.push_back(std::move(message)); + auto message = std::make_shared(); + message->event_id = event_id_str; + message->user_id = user_it->second; + message->body = body_json.asString(); + message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); + message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); + message->type = MessageType::VIDEO; + message->is_reply = is_reply; + new_messages.push_back(message); + room_data->message_by_event_id[event_id_str] = message; } } @@ -746,6 +771,91 @@ namespace QuickMedia { return PluginResult::OK; } + static std::string create_body_for_message_reply(const RoomData *room_data, const Message *message, const std::string &body) { + std::string related_to_body; + switch(message->type) { + case MessageType::TEXT: + related_to_body = message->body; + break; + case MessageType::IMAGE: + related_to_body = "sent an image"; + break; + case MessageType::VIDEO: + related_to_body = "sent a video"; + break; + } + return "> <" + room_data->user_info[message->user_id].user_id + "> " + std::move(related_to_body) + "\n\n" + body; + } + + // TODO: Add formatted_body just like element does with event_id; + + Json::Value relates_to_json(Json::objectValue); + relates_to_json["m.in_reply_to"] = std::move(in_reply_to_json); + + Json::Value request_data(Json::objectValue); + request_data["msgtype"] = message_type_to_request_msg_type_str(MessageType::TEXT); + request_data["body"] = create_body_for_message_reply(room_it->second.get(), relates_to_message, body); + request_data["m.relates_to"] = std::move(relates_to_json); + + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = ""; + + std::vector additional_args = { + { "-X", "PUT" }, + { "-H", "content-type: application/json" }, + { "-H", "Authorization: Bearer " + access_token }, + { "--data-binary", Json::writeString(builder, std::move(request_data)) } + }; + + char request_url[512]; + snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); + fprintf(stderr, "Post message to |%s|\n", request_url); + + std::string server_response; + if(download_to_string(request_url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + return PluginResult::NET_ERR; + + if(server_response.empty()) + return PluginResult::ERR; + + Json::Value json_root; + Json::CharReaderBuilder json_builder; + std::unique_ptr json_reader(json_builder.newCharReader()); + std::string json_errors; + if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &json_root, &json_errors)) { + fprintf(stderr, "Matrix post message response parse error: %s\n", json_errors.c_str()); + return PluginResult::ERR; + } + + if(!json_root.isObject()) + return PluginResult::ERR; + + const Json::Value &event_id_json = json_root["event_id"]; + if(!event_id_json.isString()) + return PluginResult::ERR; + + fprintf(stderr, "Matrix post reply, response event id: %s\n", event_id_json.asCString()); + return PluginResult::OK; + } + // Returns empty string on error static const char* file_get_filename(const std::string &filepath) { size_t index = filepath.rfind('/'); diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp index 04a71f2..186852b 100644 --- a/src/plugins/NyaaSi.cpp +++ b/src/plugins/NyaaSi.cpp @@ -26,8 +26,8 @@ namespace QuickMedia { } - static std::unique_ptr create_front_page_item(const std::string &title, const std::string &category) { - auto body_item = std::make_unique(title); + static std::shared_ptr 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; } @@ -120,7 +120,7 @@ namespace QuickMedia { return SearchResult::ERR; } - auto body_item = std::make_unique(std::move(title)); + auto body_item = BodyItem::create(std::move(title)); body_item->url = std::move(link); body_item->thumbnail_url = "https://nyaa.si/static/img/icons/nyaa/" + category_id + ".png"; body_item->set_description("Published: " + pub_date + "\nSeeders: " + seeders + "\nLeechers: " + leechers + "\nDownloads: " + downloads + "\nSize: " + size + "\nComments: " + comments); @@ -171,9 +171,9 @@ namespace QuickMedia { // } // std::string torrent_url = "https://nyaa.si/download/" + id + ".torrent"; - // auto torrent_item = std::make_unique("Download torrent"); + // auto torrent_item = BodyItem::create("Download torrent"); // torrent_item->url = "https://nyaa.si/download/" + id + ".torrent"; - auto torrent_item = std::make_unique("Download magnet"); + auto torrent_item = BodyItem::create("Download magnet"); std::string magnet_url; std::string description; @@ -192,7 +192,7 @@ namespace QuickMedia { const char *href = quickmedia_html_node_get_attribute_value(node, "href"); const char *text = quickmedia_html_node_get_text(node); if(item_data->empty() && href && text && strncmp(href, "/user/", 6) == 0) { - auto body_item = std::make_unique("Submitter: " + strip(text)); + auto body_item = BodyItem::create("Submitter: " + strip(text)); body_item->url = "https://nyaa.si/" + std::string(href); item_data->push_back(std::move(body_item)); } @@ -250,7 +250,7 @@ namespace QuickMedia { 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, "/user/", 6) == 0) { - auto body_item = std::make_unique(strip(text)); + auto body_item = BodyItem::create(strip(text)); //body_item->url = "https://nyaa.si/" + std::string(href); item_data->push_back(std::move(body_item)); } diff --git a/src/plugins/Pornhub.cpp b/src/plugins/Pornhub.cpp index 9a7242a..b093c64 100644 --- a/src/plugins/Pornhub.cpp +++ b/src/plugins/Pornhub.cpp @@ -38,7 +38,7 @@ namespace QuickMedia { 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 && begins_with(href, "/view_video.php?viewkey")) { - auto item = std::make_unique(strip(title)); + auto item = BodyItem::create(strip(title)); item->url = std::string("https://www.pornhub.com") + href; result_items->push_back(std::move(item)); } @@ -93,7 +93,7 @@ namespace QuickMedia { 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 && begins_with(href, "/view_video.php?viewkey")) { - auto item = std::make_unique(strip(title)); + auto item = BodyItem::create(strip(title)); item->url = std::string("https://www.pornhub.com") + href; result_items->push_back(std::move(item)); } diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 2827d83..4126532 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -17,7 +17,7 @@ namespace QuickMedia { } } - static std::unique_ptr parse_content_video_renderer(const Json::Value &content_item_json, std::unordered_set &added_videos) { + static std::shared_ptr parse_content_video_renderer(const Json::Value &content_item_json, std::unordered_set &added_videos) { if(!content_item_json.isObject()) return nullptr; @@ -68,7 +68,7 @@ namespace QuickMedia { if(!title) return nullptr; - auto body_item = std::make_unique(title); + auto body_item = BodyItem::create(title); /* TODO: Make date a different color */ std::string date_str; if(date) @@ -170,7 +170,7 @@ namespace QuickMedia { continue; const Json::Value &rich_item_contents = rich_item_renderer_json["content"]; - std::unique_ptr body_item = parse_content_video_renderer(rich_item_contents, added_videos); + std::shared_ptr body_item = parse_content_video_renderer(rich_item_contents, added_videos); if(body_item) result_items.push_back(std::move(body_item)); } @@ -273,14 +273,14 @@ namespace QuickMedia { continue; for(const Json::Value &item_json : items_json) { - std::unique_ptr body_item = parse_content_video_renderer(item_json, added_videos); + std::shared_ptr body_item = parse_content_video_renderer(item_json, added_videos); if(body_item) result_items.push_back(std::move(body_item)); } } for(const Json::Value &content_item_json : item_contents_json) { - std::unique_ptr body_item = parse_content_video_renderer(content_item_json, added_videos); + std::shared_ptr body_item = parse_content_video_renderer(content_item_json, added_videos); if(body_item) result_items.push_back(std::move(body_item)); } @@ -444,7 +444,7 @@ namespace QuickMedia { return result.substr(0, index); } - static std::unique_ptr parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set &added_videos) { + static std::shared_ptr parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set &added_videos) { const Json::Value &compact_video_renderer_json = item_json["compactVideoRenderer"]; if(!compact_video_renderer_json.isObject()) return nullptr; @@ -487,7 +487,7 @@ namespace QuickMedia { if(!title) return nullptr; - auto body_item = std::make_unique(title); + auto body_item = BodyItem::create(title); /* TODO: Make date a different color */ std::string date_str; if(date) -- cgit v1.2.3