aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp36
-rw-r--r--src/QuickMedia.cpp108
-rw-r--r--src/plugins/Dmenu.cpp2
-rw-r--r--src/plugins/FileManager.cpp2
-rw-r--r--src/plugins/Fourchan.cpp10
-rw-r--r--src/plugins/Mangadex.cpp4
-rw-r--r--src/plugins/Manganelo.cpp6
-rw-r--r--src/plugins/Mangatown.cpp10
-rw-r--r--src/plugins/Matrix.cpp172
-rw-r--r--src/plugins/NyaaSi.cpp14
-rw-r--r--src/plugins/Pornhub.cpp4
-rw-r--r--src/plugins/Youtube.cpp14
12 files changed, 288 insertions, 94 deletions
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<BodyItem> 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<ThumbnailData> 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<BodyItem>(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<BodyItem>(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<BodyItem>(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<BodyItem> 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, &current_room_id, &new_page, &typing_message](const sf::String &text) {
+ chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, &current_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<BodyItem> 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<BodyItem>(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<BodyItem>(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<BodyItem>("/" + 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<BodyItem>(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<std::mutex> lock(board_list_mutex);
BodyItems body_items;
for(auto &cached_body_item : cached_thread_list_items) {
- body_items.push_back(std::make_unique<BodyItem>(*cached_body_item));
+ body_items.push_back(std::make_shared<BodyItem>(*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<BodyItem>(""));
+ 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<BodyItem>("");
+ 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<BodyItem>(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<BodyItem>(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<BodyItem>(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<BodyItem>(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<BodyItem>(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<BodyItem>(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<BodyItem>(""));
+ result_items.push_back(BodyItem::create(""));
continue;
}
- result_items.push_back(std::make_unique<BodyItem>(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<BodyItem>(""));
+ 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<BodyItem>(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<BodyItem>(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<Message> *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<BodyItem>("");
+ 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<Message> new_messages;
+ std::vector<std::shared_ptr<Message>> 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>();
+ 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>();
+ 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>();
+ 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 <mx-reply><blockquote... and also support greentext with that
+ PluginResult Matrix::post_reply(const std::string &room_id, const std::string &body, void *relates_to) {
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end()) {
+ fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
+ return PluginResult::ERR;
+ }
+
+ Message *relates_to_message = (Message*)relates_to;
+
+ char random_characters[18];
+ if(!generate_random_characters(random_characters, sizeof(random_characters)))
+ return PluginResult::ERR;
+
+ std::string random_readable_chars = random_characters_to_readable_string(random_characters, sizeof(random_characters));
+
+ Json::Value in_reply_to_json(Json::objectValue);
+ in_reply_to_json["event_id"] = relates_to_message->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<CommandArg> 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::CharReader> 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<BodyItem> create_front_page_item(const std::string &title, const std::string &category) {
- auto body_item = std::make_unique<BodyItem>(title);
+ 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;
}
@@ -120,7 +120,7 @@ namespace QuickMedia {
return SearchResult::ERR;
}
- auto body_item = std::make_unique<BodyItem>(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<BodyItem>("Download torrent");
+ // auto torrent_item = BodyItem::create("Download torrent");
// torrent_item->url = "https://nyaa.si/download/" + id + ".torrent";
- auto torrent_item = std::make_unique<BodyItem>("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<BodyItem>("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<BodyItem>(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<BodyItem>(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<BodyItem>(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<BodyItem> parse_content_video_renderer(const Json::Value &content_item_json, std::unordered_set<std::string> &added_videos) {
+ 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;
@@ -68,7 +68,7 @@ namespace QuickMedia {
if(!title)
return nullptr;
- auto body_item = std::make_unique<BodyItem>(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<BodyItem> body_item = parse_content_video_renderer(rich_item_contents, added_videos);
+ 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));
}
@@ -273,14 +273,14 @@ namespace QuickMedia {
continue;
for(const Json::Value &item_json : items_json) {
- std::unique_ptr<BodyItem> body_item = parse_content_video_renderer(item_json, added_videos);
+ std::shared_ptr<BodyItem> 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<BodyItem> body_item = parse_content_video_renderer(content_item_json, added_videos);
+ std::shared_ptr<BodyItem> 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<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set<std::string> &added_videos) {
+ 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;
@@ -487,7 +487,7 @@ namespace QuickMedia {
if(!title)
return nullptr;
- auto body_item = std::make_unique<BodyItem>(title);
+ auto body_item = BodyItem::create(title);
/* TODO: Make date a different color */
std::string date_str;
if(date)