aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-11-01 12:19:30 +0100
committerdec05eba <dec05eba@protonmail.com>2020-11-01 12:19:30 +0100
commit7292fe11254109266e7adb9937e5f0fa797711f6 (patch)
treea581c05e0f0b795646fb1458ce118adf5bb5bd88
parent2278d52b59e9818f08bd73ec6583a1589d9512f2 (diff)
Matrix: add invites tab, add /leave command, remove room when leaving, add async loading for more tasks
-rw-r--r--README.md1
-rw-r--r--TODO14
-rw-r--r--include/Body.hpp4
-rw-r--r--include/QuickMedia.hpp8
-rw-r--r--plugins/Matrix.hpp98
-rw-r--r--src/QuickMedia.cpp68
-rw-r--r--src/plugins/Matrix.cpp598
7 files changed, 642 insertions, 149 deletions
diff --git a/README.md b/README.md
index 53a2335..75e6ad6 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,7 @@ In matrix you can select a message with enter to open the url in the message (or
## Matrix commands
`/upload` to upload an image.\
`/logout` to logout.\
+`/leave` to leave the current room.\
`/me` to send a message of type "m.emote".
# 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
diff --git a/TODO b/TODO
index a1020ca..cb6624e 100644
--- a/TODO
+++ b/TODO
@@ -46,7 +46,7 @@ Provide a way to specify when notifications should be received (using matrix api
Use quickmedia to show image in matrix rooms, instead of mpv.
Respect ~/.Xresources dpi (read the file, loop lines and look for Xft.dpi). Fallback to 96 dpi.
Merge body items in matrix if they are posted by the same author (there is a git stash for this).
-Add joining/leaving room in matrix, and also show invites and add command to ban users. Also add joining by invite, and show invites in the rooms list.
+Add joining/leaving room in matrix, and also show invites and add command to ban users.
Support peertube (works with mpv, but need to implement search and related videos).
Scroll to bottom when receiving a new message even if the selected message is not the last one. It should instead scroll if the last message is visible on the screen.
Also add a tab for common directories and recently accessed files/directories (the directories would be the directory of used files).
@@ -81,7 +81,6 @@ Remove related videos that have already been watched (except the first related v
Use GET /_matrix/client/r0/notifications to get the exact messages that notified us in matrix.
Add F5 to refresh page.
Support m.sticker, m.direct, and other matrix events.
-Show invites for us in matrix in a seperate tag and show notification when receiving invite.
Allow choosing which translation/scanlation to use on mangadex. Right now it uses the latest one, which is most likely to be the best.
Add file upload to 4chan.
Retry download if it fails, at least 3 times (observed to be needed for mangadex images).
@@ -122,4 +121,13 @@ Cancel video download when pressing escape or closing window (important in matri
Support webp.
Reload history/recommendations after closing a video.
Show images while they download by showing them as scanlines starting from the top. Needed for slow websites such as 4chan.
-Use curl parallel download instead of downloading with multiple threads. \ No newline at end of file
+Use curl parallel download instead of downloading with multiple threads.
+Handle matrix groups? (which also contains join, invite, leave...).
+Add functionality to ignore users in matrix. This is done with an ignore request and we wont get messages and invites from that user anymore. Also add option to ignore in the invites page.
+Hide invites tab when there are no invites (to make room for other tabs in the future).
+Add keybind to go to invites page from any page.
+Show marker beside pinned messages tab name if there are new pinned messages.
+Make /logout work everywhere, not only in room message input.
+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).
+Disable message input in matrix when muted.
+Preview rooms? \ No newline at end of file
diff --git a/include/Body.hpp b/include/Body.hpp
index 1a82443..99f8b1c 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -59,8 +59,9 @@ namespace QuickMedia {
dirty_author = true;
}
+ // |new_timestamp| is in milliseconds
void set_timestamp(time_t new_timestamp) {
- if(timestamp == 0 && new_timestamp == 0)
+ if(new_timestamp == timestamp)
return;
timestamp = new_timestamp;
dirty_timestamp = true;
@@ -90,6 +91,7 @@ namespace QuickMedia {
const std::string& get_title() const { return title; }
const std::string& get_description() const { return description; }
const std::string& get_author() const { return author; }
+ // In milliseconds
time_t get_timestamp() const { return timestamp; }
sf::Color get_title_color() const { return title_color; }
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 821a427..c043962 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -59,6 +59,10 @@ namespace QuickMedia {
bool is_window_focused();
RoomData* get_current_chat_room();
+
+ void set_go_to_previous_page();
+
+ TaskResult run_task_with_loading_screen(std::function<bool()> callback);
private:
void base_event_handler(sf::Event &event, PageType previous_page, Body *body, SearchBar *search_bar, bool handle_key_press = true, bool handle_searchbar = true);
void page_loop(std::vector<Tab> &tabs);
@@ -71,8 +75,6 @@ namespace QuickMedia {
void chat_page(MatrixChatPage *chat_page, RoomData *current_room);
void after_matrix_login_page();
- TaskResult run_task_with_loading_screen(std::function<bool()> callback);
-
enum class LoadImageResult {
OK,
FAILED,
@@ -135,5 +137,7 @@ namespace QuickMedia {
std::vector<std::string> selected_files;
bool fit_image_to_window = false;
RoomData *current_chat_room = nullptr;
+ bool go_to_previous_page = false;
+ std::thread::id main_thread_id;
};
} \ No newline at end of file
diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp
index 281f5a8..3d3c4b2 100644
--- a/plugins/Matrix.hpp
+++ b/plugins/Matrix.hpp
@@ -49,7 +49,7 @@ namespace QuickMedia {
sf::Vector2i thumbnail_size; // Set to {0, 0} if not specified
RelatedEventType related_event_type;
bool mentions_me = false;
- time_t timestamp = 0;
+ time_t timestamp = 0; // In milliseconds
MessageType type;
};
@@ -91,6 +91,8 @@ namespace QuickMedia {
void set_pinned_events(std::vector<std::string> new_pinned_events);
std::set<std::string>& get_tags_unsafe();
+ void clear_data();
+
std::string id;
bool initial_fetch_finished = false;
@@ -126,6 +128,14 @@ namespace QuickMedia {
std::set<std::string> tags;
};
+ struct Invite {
+ std::string room_name;
+ std::string room_avatar_url;
+ std::shared_ptr<UserInfo> invited_by;
+ time_t timestamp = 0; // In milliseconds
+ bool new_invite = false;
+ };
+
enum class MessageDirection {
BEFORE,
AFTER
@@ -156,46 +166,65 @@ namespace QuickMedia {
CHAT
};
+ enum class LeaveType {
+ LEAVE,
+ KICKED,
+ BANNED
+ };
+
class MatrixDelegate {
public:
virtual ~MatrixDelegate() = default;
- virtual void room_create(RoomData *room) = 0;
+ virtual void join_room(RoomData *room) = 0;
+ virtual void leave_room(RoomData *room, LeaveType leave_type, const std::string &reason) = 0;
+
// Note: calling |room| methods inside this function is not allowed
virtual void room_add_tag(RoomData *room, const std::string &tag) = 0;
// Note: calling |room| methods inside this function is not allowed
virtual void room_remove_tag(RoomData *room, const std::string &tag) = 0;
virtual void room_add_new_messages(RoomData *room, const Messages &messages, bool is_initial_sync) = 0;
+ virtual void add_invite(const std::string &room_id, const Invite &invite) = 0;
+ virtual void remove_invite(const std::string &room_id) = 0;
+
virtual void update(MatrixPageType page_type) { (void)page_type; }
};
class Matrix;
class MatrixRoomsPage;
class MatrixRoomTagsPage;
+ class MatrixInvitesPage;
+ class MatrixChatPage;
class MatrixQuickMedia : public MatrixDelegate {
public:
- MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page);
+ MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page);
- void room_create(RoomData *room) override;
+ void join_room(RoomData *room) override;
+ void leave_room(RoomData *room, LeaveType leave_type, const std::string &reason) override;
void room_add_tag(RoomData *room, const std::string &tag) override;
void room_remove_tag(RoomData *room, const std::string &tag) override;
void room_add_new_messages(RoomData *room, const Messages &messages, bool is_initial_sync) override;
+ void add_invite(const std::string &room_id, const Invite &invite) override;
+ void remove_invite(const std::string &room_id) override;
+
void update(MatrixPageType page_type) override;
Program *program;
Matrix *matrix;
MatrixRoomsPage *rooms_page;
MatrixRoomTagsPage *room_tags_page;
+ MatrixInvitesPage *invites_page;
+ private:
+ void update_pending_room_messages(MatrixPageType page_type);
private:
struct RoomMessagesData {
Messages messages;
bool is_initial_sync;
};
- std::vector<std::shared_ptr<BodyItem>> room_body_items;
std::map<RoomData*, std::shared_ptr<BodyItem>> room_body_item_by_room;
std::map<RoomData*, RoomMessagesData> pending_room_messages;
std::mutex pending_room_messages_mutex;
@@ -213,14 +242,19 @@ namespace QuickMedia {
void add_body_item(std::shared_ptr<BodyItem> body_item);
void move_room_to_top(RoomData *room);
+ void remove_body_item_by_room_id(const std::string &room_id);
+
+ void set_current_chat_page(MatrixChatPage *chat_page);
MatrixQuickMedia *matrix_delegate = nullptr;
private:
std::mutex mutex;
std::vector<std::shared_ptr<BodyItem>> room_body_items;
+ std::vector<std::string> pending_remove_body_items;
Body *body;
std::string title;
MatrixRoomTagsPage *room_tags_page;
+ MatrixChatPage *current_chat_page;
};
class MatrixRoomTagsPage : public Page {
@@ -234,20 +268,56 @@ namespace QuickMedia {
void remove_room_body_item_from_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag);
void move_room_to_top(RoomData *room);
+ void remove_body_item_by_room_id(const std::string &room_id);
+
+ void set_current_rooms_page(MatrixRoomsPage *rooms_page);
MatrixQuickMedia *matrix_delegate = nullptr;
- MatrixRoomsPage *current_rooms_page = nullptr;
private:
struct TagData {
std::shared_ptr<BodyItem> tag_item;
std::vector<std::shared_ptr<BodyItem>> room_body_items;
};
- std::mutex mutex;
+ std::recursive_mutex mutex;
Body *body;
std::map<std::string, TagData> tag_body_items_by_name;
std::map<std::string, std::vector<std::shared_ptr<BodyItem>>> add_room_body_items_by_tags;
std::map<std::string, std::vector<std::shared_ptr<BodyItem>>> remove_room_body_items_by_tags;
+ MatrixRoomsPage *current_rooms_page = nullptr;
+ };
+
+ class MatrixInvitesPage : public Page {
+ public:
+ MatrixInvitesPage(Program *program, Matrix *matrix, Body *body);
+
+ const char* get_title() const override { return title.c_str(); }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
+
+ void update() override;
+ void add_body_item(std::shared_ptr<BodyItem> body_item);
+ void remove_body_item_by_room_id(const std::string &room_id);
+ private:
+ Matrix *matrix;
+ std::mutex mutex;
+ std::vector<std::shared_ptr<BodyItem>> body_items;
+ std::vector<std::string> pending_remove_body_items;
+ Body *body;
+ std::string title = "Invites (0)";
+ size_t prev_invite_count = 0;
+ };
+
+ class MatrixInviteDetailsPage : public Page {
+ public:
+ MatrixInviteDetailsPage(Program *program, Matrix *matrix, MatrixInvitesPage *invites_page, std::string room_id, std::string title) :
+ Page(program), matrix(matrix), invites_page(invites_page), room_id(std::move(room_id)), title(std::move(title)) {}
+ const char* get_title() const override { return title.c_str(); }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
+
+ Matrix *matrix;
+ MatrixInvitesPage *invites_page;
+ const std::string room_id;
+ const std::string title;
};
// Dummy, only play one video. TODO: Play all videos in room, as related videos?
@@ -266,7 +336,9 @@ namespace QuickMedia {
class MatrixChatPage : public Page {
public:
- MatrixChatPage(Program *program, std::string room_id) : Page(program), room_id(std::move(room_id)) {}
+ MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page);
+ ~MatrixChatPage() override;
+
const char* get_title() const override { return ""; }
PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override {
(void)title;
@@ -279,6 +351,7 @@ namespace QuickMedia {
const std::string room_id;
MatrixQuickMedia *matrix_delegate = nullptr;
+ MatrixRoomsPage *rooms_page = nullptr;
};
class Matrix {
@@ -314,6 +387,9 @@ namespace QuickMedia {
PluginResult set_read_marker(RoomData *room, const Message *message);
+ PluginResult join_room(const std::string &room_id);
+ PluginResult leave_room(const std::string &room_id);
+
// |message| is from |BodyItem.userdata| and is of type |Message*|
bool was_message_posted_by_me(void *message);
@@ -341,14 +417,16 @@ namespace QuickMedia {
void events_set_room_name(const rapidjson::Value &events_json, RoomData *room_data);
void events_add_pinned_events(const rapidjson::Value &events_json, RoomData *room_data);
void events_add_room_to_tags(const rapidjson::Value &events_json, RoomData *room_data, MatrixDelegate *delegate);
+ void add_invites(const rapidjson::Value &invite_json, MatrixDelegate *delegate);
+ void remove_rooms(const rapidjson::Value &leave_json, MatrixDelegate *delegate);
std::shared_ptr<Message> parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data);
PluginResult upload_file(RoomData *room, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg);
void add_room(std::unique_ptr<RoomData> room);
+ void remove_room(const std::string &room_id);
DownloadResult download_json(rapidjson::Document &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr) const;
private:
std::vector<std::unique_ptr<RoomData>> rooms;
std::unordered_map<std::string, size_t> room_data_by_id; // value is an index into |rooms|
- size_t room_list_read_index = 0;
std::mutex room_data_mutex;
std::string user_id;
std::string username;
@@ -357,6 +435,8 @@ namespace QuickMedia {
std::optional<int> upload_limit;
std::string next_batch;
+ std::unordered_map<std::string, Invite> invites;
+
std::thread sync_thread;
bool sync_running = false;
};
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index dc3d167..e6d7b9a 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -353,6 +353,8 @@ namespace QuickMedia {
if(create_directory_recursive(get_cache_dir().join("thumbnails")) != 0) {
fprintf(stderr, "Failed to create thumbnails directory\n");
}
+
+ main_thread_id = std::this_thread::get_id();
}
Program::~Program() {
@@ -586,22 +588,19 @@ namespace QuickMedia {
if(matrix) {
matrix->use_tor = use_tor;
- {
- auto window_size_u = window.getSize();
- window_size.x = window_size_u.x;
- window_size.y = window_size_u.y;
- sf::Text loading_text("Loading...", *FontLoader::get_font(FontLoader::FontType::LATIN), 24);
- loading_text.setPosition(window_size.x * 0.5f - loading_text.getLocalBounds().width * 0.5f, window_size.y * 0.5f - loading_text.getLocalBounds().height * 0.5f);
- window.clear(back_color);
- window.draw(loading_text);
- window.display();
- }
- if(matrix->load_and_verify_cached_session() == PluginResult::OK) {
+
+ TaskResult task_result = run_task_with_loading_screen([this]() {
+ return matrix->load_and_verify_cached_session() == PluginResult::OK;
+ });
+
+ if(task_result == TaskResult::TRUE) {
current_page = PageType::CHAT;
- } else {
+ } else if(task_result == TaskResult::FALSE) {
fprintf(stderr, "Failed to load session cache, redirecting to login page\n");
current_page = PageType::CHAT_LOGIN;
chat_login_page();
+ } else {
+ exit(exit_code);
}
after_matrix_login_page();
@@ -882,6 +881,10 @@ namespace QuickMedia {
return current_chat_room;
}
+ void Program::set_go_to_previous_page() {
+ go_to_previous_page = true;
+ }
+
static void select_body_item_by_room(Body *body, RoomData *room) {
for(size_t i = 0; i < body->items.size(); ++i) {
auto &body_item = body->items[i];
@@ -1327,6 +1330,11 @@ namespace QuickMedia {
}
window.display();
+
+ if(go_to_previous_page) {
+ go_to_previous_page = false;
+ goto page_end;
+ }
}
page_end:
@@ -1500,6 +1508,8 @@ namespace QuickMedia {
}
TaskResult Program::run_task_with_loading_screen(std::function<bool()> callback) {
+ assert(std::this_thread::get_id() == main_thread_id);
+
std::promise<bool> result_promise;
std::future<bool> future = result_promise.get_future();
std::thread task_thread([](std::promise<bool> &&promise, std::function<bool()> callback) {
@@ -1642,7 +1652,7 @@ namespace QuickMedia {
if(err != VideoPlayer::Error::OK) {
std::string err_msg = "Failed to play url: ";
err_msg += video_url;
- show_notification("Video player", err_msg.c_str(), Urgency::CRITICAL);
+ show_notification("QuickMedia", err_msg.c_str(), Urgency::CRITICAL);
current_page = previous_page;
} else {
related_media_body->clear_items();
@@ -1658,7 +1668,7 @@ namespace QuickMedia {
std::string err_msg = "Failed to extract id of youtube url ";
err_msg += video_url;
err_msg + ", video wont be saved in history";
- show_notification("Video player", err_msg.c_str(), Urgency::LOW);
+ show_notification("QuickMedia", err_msg.c_str(), Urgency::LOW);
return;
}
@@ -1718,7 +1728,7 @@ namespace QuickMedia {
// If there are no videos to play, then dont play any...
if(new_video_url.empty()) {
- show_notification("Video player", "No more related videos to play");
+ show_notification("QuickMedia", "No more related videos to play");
current_page = previous_page;
return;
}
@@ -1865,7 +1875,7 @@ namespace QuickMedia {
VideoPlayer::Error update_err = video_player->update();
if(update_err == VideoPlayer::Error::FAIL_TO_CONNECT_TIMEOUT) {
- show_notification("Video player", "Failed to connect to mpv ipc after 10 seconds", Urgency::CRITICAL);
+ show_notification("QuickMedia", "Failed to connect to mpv ipc after 10 seconds", Urgency::CRITICAL);
current_page = previous_page;
break;
} else if(update_err == VideoPlayer::Error::EXITED && video_player->exit_status == 0) {
@@ -1873,7 +1883,7 @@ namespace QuickMedia {
current_page = previous_page;
break;
} else if(update_err != VideoPlayer::Error::OK) {
- show_notification("Video player", "The video player failed to play the video (error code " + std::to_string((int)update_err) + ")", Urgency::CRITICAL);
+ show_notification("QuickMedia", "The video player failed to play the video (error code " + std::to_string((int)update_err) + ")", Urgency::CRITICAL);
current_page = previous_page;
break;
}
@@ -3278,6 +3288,16 @@ namespace QuickMedia {
chat_input.set_editable(false);
chat_state = ChatState::NAVIGATING;
return true;
+ } else if(text == "/leave") {
+ TaskResult task_result = run_task_with_loading_screen([this, &current_room]() {
+ return matrix->leave_room(current_room->id) == PluginResult::OK;
+ });
+ if(task_result != TaskResult::FALSE) {
+ go_to_previous_page = true;
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
+ }
+ return true;
} else if(strncmp(text.c_str(), "/me ", 4) == 0) {
msgtype = "m.emote";
text.erase(text.begin(), text.begin() + 4);
@@ -4140,6 +4160,11 @@ namespace QuickMedia {
}
window.display();
+
+ if(go_to_previous_page) {
+ go_to_previous_page = false;
+ goto chat_page_end;
+ }
}
chat_page_end:
@@ -4172,12 +4197,17 @@ namespace QuickMedia {
rooms_tags_body->thumbnail_mask_shader = &circle_mask_shader;
auto matrix_rooms_tag_page = std::make_unique<MatrixRoomTagsPage>(this, rooms_tags_body.get());
- MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get());
+ auto invites_body = create_body();
+ invites_body->thumbnail_mask_shader = &circle_mask_shader;
+ auto matrix_invites_page = std::make_unique<MatrixInvitesPage>(this, matrix, invites_body.get());
+
+ MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get(), matrix_invites_page.get());
matrix->start_sync(&matrix_handler);
std::vector<Tab> tabs;
tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ tabs.push_back(Tab{std::move(invites_body), std::move(matrix_invites_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
sf::Sprite load_sprite(loading_icon);
sf::Vector2u loading_icon_size = loading_icon.getSize();
@@ -4194,6 +4224,8 @@ namespace QuickMedia {
window_size.y = event.size.height;
sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
window.setView(sf::View(visible_area));
+ } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
+ window.close();
}
}
window.clear(back_color);
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 93eb1d6..b8c2f89 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -70,6 +70,15 @@ static std::string extract_first_line_elipses(const std::string &str, size_t max
}
namespace QuickMedia {
+ static void remove_body_item_by_url(BodyItems &body_items, const std::string &url) {
+ for(auto it = body_items.begin(); it != body_items.end();) {
+ if((*it)->url == url)
+ it = body_items.erase(it);
+ else
+ ++it;
+ }
+ }
+
std::shared_ptr<UserInfo> RoomData::get_user_by_id(const std::string &user_id) {
std::lock_guard<std::mutex> lock(room_mutex);
auto user_it = user_info_by_user_id.find(user_id);
@@ -203,12 +212,24 @@ namespace QuickMedia {
return tags;
}
- MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page) : program(program), matrix(matrix), rooms_page(rooms_page), room_tags_page(room_tags_page) {
+ void RoomData::clear_data() {
+ std::lock_guard<std::mutex> lock(room_mutex);
+ fetched_messages_by_event_id.clear();
+ user_info_by_user_id.clear();
+ messages.clear();
+ message_by_event_id.clear();
+ pinned_events.clear();
+ tags.clear();
+ }
+
+ MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page) :
+ program(program), matrix(matrix), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page)
+ {
rooms_page->matrix_delegate = this;
room_tags_page->matrix_delegate = this;
}
- void MatrixQuickMedia::room_create(RoomData *room) {
+ void MatrixQuickMedia::join_room(RoomData *room) {
std::string room_name = room->get_name();
if(room_name.empty())
room_name = room->id;
@@ -220,11 +241,18 @@ namespace QuickMedia {
body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
body_item->thumbnail_size = sf::Vector2i(32, 32);
room->userdata = body_item.get();
- room_body_items.push_back(body_item);
rooms_page->add_body_item(body_item);
room_body_item_by_room[room] = body_item;
}
+ void MatrixQuickMedia::leave_room(RoomData *room, LeaveType leave_type, const std::string &reason) {
+ room_body_item_by_room.erase(room);
+ rooms_page->remove_body_item_by_room_id(room->id);
+ room_tags_page->remove_body_item_by_room_id(room->id);
+ if(leave_type != LeaveType::LEAVE)
+ show_notification("QuickMedia", reason);
+ }
+
void MatrixQuickMedia::room_add_tag(RoomData *room, const std::string &tag) {
room_tags_page->add_room_body_item_to_tag(room_body_item_by_room[room], tag);
}
@@ -240,6 +268,23 @@ namespace QuickMedia {
room_messages_data.is_initial_sync = is_initial_sync;
}
+ void MatrixQuickMedia::add_invite(const std::string &room_id, const Invite &invite) {
+ auto body_item = BodyItem::create(invite.room_name);
+ body_item->set_description("Invited by " + invite.invited_by->display_name + " (" + invite.invited_by->user_id + ")");
+ body_item->url = room_id;
+ body_item->thumbnail_url = invite.room_avatar_url;
+ body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ body_item->thumbnail_size = sf::Vector2i(32, 32);
+ body_item->set_timestamp(invite.timestamp);
+ invites_page->add_body_item(std::move(body_item));
+ if(invite.new_invite)
+ show_notification("QuickMedia matrix - " + invite.room_name, "You were invited to " + invite.room_name + " by " + invite.invited_by->display_name + " (" + invite.invited_by->user_id + ")");
+ }
+
+ void MatrixQuickMedia::remove_invite(const std::string &room_id) {
+ invites_page->remove_body_item_by_room_id(room_id);
+ }
+
static int find_top_body_position_for_unread_room(const BodyItems &room_body_items, BodyItem *item_to_swap) {
for(int i = 0; i < (int)room_body_items.size(); ++i) {
const auto &body_item = room_body_items[i];
@@ -269,6 +314,10 @@ namespace QuickMedia {
}
void MatrixQuickMedia::update(MatrixPageType page_type) {
+ update_pending_room_messages(page_type);
+ }
+
+ void MatrixQuickMedia::update_pending_room_messages(MatrixPageType page_type) {
std::lock_guard<std::mutex> lock(pending_room_messages_mutex);
bool is_window_focused = program->is_window_focused();
RoomData *current_room = program->get_current_chat_room();
@@ -337,17 +386,17 @@ namespace QuickMedia {
MatrixRoomsPage::MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page) : Page(program), body(body), title(std::move(title)), room_tags_page(room_tags_page) {
if(room_tags_page)
- room_tags_page->current_rooms_page = this;
+ room_tags_page->set_current_rooms_page(this);
}
MatrixRoomsPage::~MatrixRoomsPage() {
if(room_tags_page)
- room_tags_page->current_rooms_page = nullptr;
+ room_tags_page->set_current_rooms_page(nullptr);
}
PluginResult MatrixRoomsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
(void)title;
- auto chat_page = std::make_unique<MatrixChatPage>(program, url);
+ auto chat_page = std::make_unique<MatrixChatPage>(program, url, this);
chat_page->matrix_delegate = matrix_delegate;
result_tabs.push_back(Tab{nullptr, std::move(chat_page), nullptr});
return PluginResult::OK;
@@ -356,6 +405,17 @@ namespace QuickMedia {
void MatrixRoomsPage::update() {
{
std::lock_guard<std::mutex> lock(mutex);
+ for(const std::string &room_id : pending_remove_body_items) {
+ remove_body_item_by_url(body->items, room_id);
+ // TODO: There can be a race condition where current_chat_page is set after entering a room and then we will enter a room we left
+ if(current_chat_page && current_chat_page->room_id == room_id) {
+ program->set_go_to_previous_page();
+ body->select_first_item();
+ current_chat_page = nullptr;
+ }
+ }
+ pending_remove_body_items.clear();
+ body->clamp_selection();
body->append_items(std::move(room_body_items));
}
matrix_delegate->update(MatrixPageType::ROOM_LIST);
@@ -389,9 +449,19 @@ namespace QuickMedia {
}
}
+ void MatrixRoomsPage::remove_body_item_by_room_id(const std::string &room_id) {
+ std::lock_guard<std::mutex> lock(mutex);
+ pending_remove_body_items.push_back(room_id);
+ }
+
+ void MatrixRoomsPage::set_current_chat_page(MatrixChatPage *chat_page) {
+ std::lock_guard<std::mutex> lock(mutex);
+ current_chat_page = chat_page;
+ }
+
PluginResult MatrixRoomTagsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
(void)title;
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
auto body = create_body();
Body *body_ptr = body.get();
TagData &tag_data = tag_body_items_by_name[url];
@@ -406,7 +476,7 @@ namespace QuickMedia {
// TODO: Also add/remove body items to above body (in submit)
void MatrixRoomTagsPage::update() {
{
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
for(auto &it : remove_room_body_items_by_tags) {
auto tag_body_it = tag_body_items_by_name.find(it.first);
if(tag_body_it == tag_body_items_by_name.end())
@@ -455,22 +525,116 @@ namespace QuickMedia {
}
void MatrixRoomTagsPage::add_room_body_item_to_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag) {
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
add_room_body_items_by_tags[tag].push_back(body_item);
}
void MatrixRoomTagsPage::remove_room_body_item_from_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag) {
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
remove_room_body_items_by_tags[tag].push_back(body_item);
}
void MatrixRoomTagsPage::move_room_to_top(RoomData *room) {
+ std::lock_guard<std::recursive_mutex> lock(mutex);
if(current_rooms_page)
current_rooms_page->move_room_to_top(room);
}
+ void MatrixRoomTagsPage::remove_body_item_by_room_id(const std::string &room_id) {
+ std::lock_guard<std::recursive_mutex> lock(mutex);
+ for(auto it = tag_body_items_by_name.begin(); it != tag_body_items_by_name.end();) {
+ remove_body_item_by_url(it->second.room_body_items, room_id);
+ if(it->second.room_body_items.empty())
+ it = tag_body_items_by_name.erase(it);
+ else
+ ++it;
+ }
+ if(current_rooms_page)
+ current_rooms_page->remove_body_item_by_room_id(room_id);
+ }
+
+ void MatrixRoomTagsPage::set_current_rooms_page(MatrixRoomsPage *rooms_page) {
+ std::lock_guard<std::recursive_mutex> lock(mutex);
+ current_rooms_page = rooms_page;
+ }
+
+ MatrixInvitesPage::MatrixInvitesPage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) {
+
+ }
+
+ PluginResult MatrixInvitesPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ auto body = create_body();
+ body->items.push_back(BodyItem::create("Accept"));
+ body->items.push_back(BodyItem::create("Decline"));
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<MatrixInviteDetailsPage>(program, matrix, this, url, "Invite to " + title), nullptr});
+ return PluginResult::OK;
+ }
+
+ PluginResult MatrixInviteDetailsPage::submit(const std::string &title, const std::string&, std::vector<Tab>&) {
+ TaskResult task_result = program->run_task_with_loading_screen([this, title]() {
+ if(title == "Accept")
+ return matrix->join_room(room_id) == PluginResult::OK;
+ else if(title == "Decline")
+ return matrix->leave_room(room_id) == PluginResult::OK;
+ return false;
+ });
+
+ if(task_result == TaskResult::TRUE) {
+ invites_page->remove_body_item_by_room_id(room_id);
+ } else if(task_result == TaskResult::FALSE) {
+ std::string action_str;
+ if(title == "Accept")
+ action_str = "accept";
+ else if(title == "Decline")
+ action_str = "decline";
+ show_notification("QuickMedia", "Failed to " + action_str + " the room invite", Urgency::CRITICAL);
+ }
+
+ program->set_go_to_previous_page();
+ return PluginResult::OK;
+ }
+
+ void MatrixInvitesPage::update() {
+ std::lock_guard<std::mutex> lock(mutex);
+
+ for(const std::string &room_id : pending_remove_body_items) {
+ remove_body_item_by_url(body->items, room_id);
+ }
+ pending_remove_body_items.clear();
+ body->clamp_selection();
+
+ // TODO: Insert in reverse order (to show the latest invite at the top?)
+ body->insert_items_by_timestamps(std::move(body_items));
+ if(body->items.size() != prev_invite_count) {
+ prev_invite_count = body->items.size();
+ title = "Invites (" + std::to_string(body->items.size()) + ")";
+ }
+ }
+
+ void MatrixInvitesPage::add_body_item(std::shared_ptr<BodyItem> body_item) {
+ std::lock_guard<std::mutex> lock(mutex);
+ body_items.push_back(std::move(body_item));
+ }
+
+ void MatrixInvitesPage::remove_body_item_by_room_id(const std::string &room_id) {
+ std::lock_guard<std::mutex> lock(mutex);
+ pending_remove_body_items.push_back(room_id);
+ }
+
+ MatrixChatPage::MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page) : Page(program), room_id(std::move(room_id)), rooms_page(rooms_page) {
+ if(rooms_page)
+ rooms_page->set_current_chat_page(this);
+ }
+
+ MatrixChatPage::~MatrixChatPage() {
+ if(rooms_page)
+ rooms_page->set_current_chat_page(nullptr);
+ }
+
void MatrixChatPage::update() {
matrix_delegate->update(MatrixPageType::CHAT);
+ if(rooms_page)
+ rooms_page->update();
}
void Matrix::start_sync(MatrixDelegate *delegate) {
@@ -641,94 +805,106 @@ namespace QuickMedia {
return PluginResult::OK;
const rapidjson::Value &join_json = GetMember(rooms_json, "join");
- if(!join_json.IsObject())
- return PluginResult::OK;
+ if(join_json.IsObject()) {
+ for(auto const &it : join_json.GetObject()) {
+ if(!it.value.IsObject())
+ continue;
- for(auto const &it : join_json.GetObject()) {
- if(!it.value.IsObject())
- continue;
+ const rapidjson::Value &room_id = it.name;
+ if(!room_id.IsString())
+ continue;
- const rapidjson::Value &room_id = it.name;
- if(!room_id.IsString())
- continue;
+ std::string room_id_str = room_id.GetString();
- std::string room_id_str = room_id.GetString();
+ bool is_new_room = false;
+ RoomData *room = get_room_by_id(room_id_str);
+ if(!room) {
+ auto new_room = std::make_unique<RoomData>();
+ new_room->id = room_id_str;
+ room = new_room.get();
+ add_room(std::move(new_room));
+ is_new_room = true;
+ }
- bool is_new_room = false;
- RoomData *room = get_room_by_id(room_id_str);
- if(!room) {
- auto new_room = std::make_unique<RoomData>();
- new_room->id = room_id_str;
- room = new_room.get();
- add_room(std::move(new_room));
- is_new_room = true;
- }
+ const rapidjson::Value &state_json = GetMember(it.value, "state");
+ if(state_json.IsObject()) {
+ const rapidjson::Value &events_json = GetMember(state_json, "events");
+ events_add_user_info(events_json, room);
+ events_set_room_name(events_json, room);
+ events_add_pinned_events(events_json, room);
+ }
- const rapidjson::Value &state_json = GetMember(it.value, "state");
- if(state_json.IsObject()) {
- const rapidjson::Value &events_json = GetMember(state_json, "events");
- events_add_user_info(events_json, room);
- events_set_room_name(events_json, room);
- events_add_pinned_events(events_json, room);
- }
+ const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral");
- const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral");
+ const rapidjson::Value &timeline_json = GetMember(it.value, "timeline");
+ if(timeline_json.IsObject()) {
+ if(!room->has_prev_batch()) {
+ // This may be non-existent if this is the first event in the room
+ const rapidjson::Value &prev_batch_json = GetMember(timeline_json, "prev_batch");
+ if(prev_batch_json.IsString())
+ room->set_prev_batch(prev_batch_json.GetString());
+ }
- const rapidjson::Value &timeline_json = GetMember(it.value, "timeline");
- if(timeline_json.IsObject()) {
- if(!room->has_prev_batch()) {
- // This may be non-existent if this is the first event in the room
- const rapidjson::Value &prev_batch_json = GetMember(timeline_json, "prev_batch");
- if(prev_batch_json.IsString())
- room->set_prev_batch(prev_batch_json.GetString());
- }
+ // TODO: Use /_matrix/client/r0/notifications ? or remove this and always look for displayname/user_id in messages
+ bool has_unread_notifications = false;
+ const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications");
+ if(unread_notification_json.IsObject()) {
+ const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count");
+ if(highlight_count_json.IsNumber() && highlight_count_json.GetInt64() > 0)
+ has_unread_notifications = true;
+ }
- // TODO: Use /_matrix/client/r0/notifications ? or remove this and always look for displayname/user_id in messages
- bool has_unread_notifications = false;
- const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications");
- if(unread_notification_json.IsObject()) {
- const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count");
- if(highlight_count_json.IsNumber() && highlight_count_json.GetInt64() > 0)
- has_unread_notifications = true;
+ const rapidjson::Value &events_json = GetMember(timeline_json, "events");
+ events_add_user_info(events_json, room);
+ events_set_room_name(events_json, room);
+ // We want to do this before adding messages to know if a message that mentions us is a new mention
+ if(ephemeral_json.IsObject()) {
+ const rapidjson::Value &events_json = GetMember(ephemeral_json, "events");
+ events_add_user_read_markers(events_json, room);
+ }
+ events_add_messages(events_json, room, MessageDirection::AFTER, delegate, has_unread_notifications);
+ events_add_pinned_events(events_json, room);
+ } else {
+ if(ephemeral_json.IsObject()) {
+ const rapidjson::Value &events_json = GetMember(ephemeral_json, "events");
+ events_add_user_read_markers(events_json, room);
+ }
}
- const rapidjson::Value &events_json = GetMember(timeline_json, "events");
- events_add_user_info(events_json, room);
- events_set_room_name(events_json, room);
- // We want to do this before adding messages to know if a message that mentions us is a new mention
- if(ephemeral_json.IsObject()) {
- const rapidjson::Value &events_json = GetMember(ephemeral_json, "events");
- events_add_user_read_markers(events_json, room);
+ auto invites_it = invites.find(room_id_str);
+ if(invites_it != invites.end()) {
+ // TODO: Show leave type and reason and who caused the invite to be removed
+ delegate->remove_invite(room_id_str);
+ invites.erase(invites_it);
}
- events_add_messages(events_json, room, MessageDirection::AFTER, delegate, has_unread_notifications);
- events_add_pinned_events(events_json, room);
- } else {
- if(ephemeral_json.IsObject()) {
- const rapidjson::Value &events_json = GetMember(ephemeral_json, "events");
- events_add_user_read_markers(events_json, room);
- }
- }
- if(is_new_room)
- delegate->room_create(room);
+ if(is_new_room)
+ delegate->join_room(room);
- const rapidjson::Value &account_data_json = GetMember(it.value, "account_data");
- if(account_data_json.IsObject()) {
- const rapidjson::Value &events_json = GetMember(account_data_json, "events");
- events_add_room_to_tags(events_json, room, delegate);
- }
-
- if(is_new_room) {
- room->acquire_room_lock();
- std::set<std::string> &room_tags = room->get_tags_unsafe();
- if(room_tags.empty()) {
- room_tags.insert(OTHERS_ROOM_TAG);
- delegate->room_add_tag(room, OTHERS_ROOM_TAG);
+ const rapidjson::Value &account_data_json = GetMember(it.value, "account_data");
+ if(account_data_json.IsObject()) {
+ const rapidjson::Value &events_json = GetMember(account_data_json, "events");
+ events_add_room_to_tags(events_json, room, delegate);
+ }
+
+ if(is_new_room) {
+ room->acquire_room_lock();
+ std::set<std::string> &room_tags = room->get_tags_unsafe();
+ if(room_tags.empty()) {
+ room_tags.insert(OTHERS_ROOM_TAG);
+ delegate->room_add_tag(room, OTHERS_ROOM_TAG);
+ }
+ room->release_room_lock();
}
- room->release_room_lock();
}
}
+ const rapidjson::Value &leave_json = GetMember(rooms_json, "leave");
+ remove_rooms(leave_json, delegate);
+
+ const rapidjson::Value &invite_json = GetMember(rooms_json, "invite");
+ add_invites(invite_json, delegate);
+
return PluginResult::OK;
}
@@ -1195,6 +1371,26 @@ namespace QuickMedia {
room_data->set_name(name_json.GetString());
}
+ for(const rapidjson::Value &event_item_json : events_json.GetArray()) {
+ if(!event_item_json.IsObject())
+ continue;
+
+ const rapidjson::Value &type_json = GetMember(event_item_json, "type");
+ if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.avatar") != 0)
+ continue;
+
+ const rapidjson::Value &content_json = GetMember(event_item_json, "content");
+ if(!content_json.IsObject())
+ continue;
+
+ const rapidjson::Value &url_json = GetMember(content_json, "url");
+ if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0)
+ continue;
+
+ std::string url_json_str = url_json.GetString() + 6;
+ room_data->set_avatar_url(homeserver + "/_matrix/media/r0/thumbnail/" + std::move(url_json_str) + "?width=32&height=32&method=crop");
+ }
+
bool has_room_name = room_data->has_name();
bool has_room_avatar_url = room_data->has_avatar_url();
@@ -1235,26 +1431,6 @@ namespace QuickMedia {
has_room_avatar_url = true;
}
}
-
- for(const rapidjson::Value &event_item_json : events_json.GetArray()) {
- if(!event_item_json.IsObject())
- continue;
-
- const rapidjson::Value &type_json = GetMember(event_item_json, "type");
- if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.avatar") != 0)
- continue;
-
- const rapidjson::Value &content_json = GetMember(event_item_json, "content");
- if(!content_json.IsObject())
- continue;
-
- const rapidjson::Value &url_json = GetMember(content_json, "url");
- if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0)
- continue;
-
- std::string url_json_str = url_json.GetString() + 6;
- room_data->set_avatar_url(homeserver + "/_matrix/media/r0/thumbnail/" + std::move(url_json_str) + "?width=32&height=32&method=crop");
- }
}
void Matrix::events_add_pinned_events(const rapidjson::Value &events_json, RoomData *room_data) {
@@ -1357,10 +1533,165 @@ namespace QuickMedia {
}
}
+ void Matrix::add_invites(const rapidjson::Value &invite_json, MatrixDelegate *delegate) {
+ if(!invite_json.IsObject())
+ return;
+
+ for(auto const &it : invite_json.GetObject()) {
+ if(!it.value.IsObject())
+ continue;
+
+ const rapidjson::Value &room_id = it.name;
+ if(!room_id.IsString())
+ continue;
+
+ const rapidjson::Value &invite_state_json = GetMember(it.value, "invite_state");
+ if(!invite_state_json.IsObject())
+ continue;
+
+ const rapidjson::Value &events_json = GetMember(invite_state_json, "events");
+ if(!events_json.IsArray())
+ continue;
+
+ for(const rapidjson::Value &event_json : events_json.GetArray()) {
+ if(!event_json.IsObject())
+ continue;
+
+ const rapidjson::Value &type_json = GetMember(event_json, "type");
+ if(!type_json.IsString())
+ continue;
+
+ if(strcmp(type_json.GetString(), "m.room.member") != 0)
+ continue;
+
+ const rapidjson::Value &content_json = GetMember(event_json, "content");
+ if(!content_json.IsObject())
+ continue;
+
+ const rapidjson::Value &sender_json = GetMember(event_json, "sender");
+ if(!sender_json.IsString())
+ continue;
+
+ const rapidjson::Value &timestamp_json = GetMember(event_json, "origin_server_ts");
+ if(!timestamp_json.IsNumber())
+ continue;
+
+ const rapidjson::Value &membership_json = GetMember(content_json, "membership");
+ if(membership_json.IsString() && strcmp(membership_json.GetString(), "invite") == 0) {
+ Invite invite;
+ RoomData invite_room;
+ events_add_user_info(events_json, &invite_room);
+ events_set_room_name(events_json, &invite_room);
+
+ auto invited_by = invite_room.get_user_by_id(sender_json.GetString());
+ if(!invited_by) {
+ fprintf(stderr, "Invited by unknown user. Bug in homeserver?\n");
+ break;
+ }
+
+ invite.room_name = invite_room.get_name();
+ invite.room_avatar_url = invite_room.get_avatar_url();
+ invite.invited_by = invited_by;
+ invite.timestamp = timestamp_json.GetInt64();
+ invite.new_invite = !next_batch.empty();
+
+ std::string room_id_str(room_id.GetString(), room_id.GetStringLength());
+ delegate->add_invite(room_id_str, invite);
+ invites[room_id_str] = std::move(invite);
+ break;
+ }
+ }
+ }
+ }
+
+ void Matrix::remove_rooms(const rapidjson::Value &leave_json, MatrixDelegate *delegate) {
+ if(!leave_json.IsObject())
+ return;
+
+ for(auto const &it : leave_json.GetObject()) {
+ if(!it.value.IsObject())
+ continue;
+
+ const rapidjson::Value &room_id = it.name;
+ if(!room_id.IsString())
+ continue;
+
+ std::string room_id_str(room_id.GetString(), room_id.GetStringLength());
+ auto invites_it = invites.find(room_id_str);
+ if(invites_it != invites.end()) {
+ // TODO: Show leave type and reason and who caused the invite to be removed
+ delegate->remove_invite(room_id_str);
+ invites.erase(invites_it);
+ }
+
+ const rapidjson::Value &timeline_json = GetMember(it.value, "timeline");
+ if(!timeline_json.IsObject())
+ continue;
+
+ const rapidjson::Value &events_json = GetMember(timeline_json, "events");
+ if(!events_json.IsArray())
+ continue;
+
+ for(const rapidjson::Value &event_json : events_json.GetArray()) {
+ if(!event_json.IsObject())
+ continue;
+
+ const rapidjson::Value &type_json = GetMember(event_json, "type");
+ if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.member") != 0)
+ continue;
+
+ const rapidjson::Value &sender_json = GetMember(event_json, "sender");
+ if(!sender_json.IsString())
+ continue;
+
+ const rapidjson::Value &content_json = GetMember(event_json, "content");
+ if(!content_json.IsObject())
+ continue;
+
+ const rapidjson::Value &membership_json = GetMember(content_json, "membership");
+ if(!membership_json.IsString())
+ continue;
+
+ std::string reason_str;
+ const rapidjson::Value &reason_json = GetMember(content_json, "reason");
+ if(reason_json.IsString())
+ reason_str = reason_json.GetString();
+
+ auto room = get_room_by_id(room_id_str);
+ if(!room)
+ continue;
+
+ std::string desc;
+ LeaveType leave_type;
+ if(strcmp(membership_json.GetString(), "leave") == 0) {
+ if(strcmp(sender_json.GetString(), user_id.c_str()) == 0) {
+ leave_type = LeaveType::LEAVE;
+ } else {
+ leave_type = LeaveType::KICKED;
+ desc = "You were kicked from " + room->get_name() + " by " + sender_json.GetString();
+ }
+ } else if(strcmp(membership_json.GetString(), "ban") == 0) {
+ leave_type = LeaveType::BANNED;
+ desc = "You were banned from " + room->get_name() + " by " + sender_json.GetString();
+ } else {
+ continue;
+ }
+
+ if(!reason_str.empty())
+ desc += ", reason: " + reason_str;
+
+ delegate->leave_room(room, leave_type, desc);
+ remove_room(room_id_str);
+ break;
+ }
+ }
+ }
+
PluginResult Matrix::get_previous_room_messages(RoomData *room_data) {
std::string from = room_data->get_prev_batch();
if(from.empty()) {
fprintf(stderr, "Info: missing previous batch for room: %s, using /sync next batch\n", room_data->id.c_str());
+ // TODO: When caching /sync, remember to add lock around getting next_batch!
from = next_batch;
if(from.empty()) {
fprintf(stderr, "Error: missing next batch!\n");
@@ -2008,12 +2339,10 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/logout", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
+ download_to_string(homeserver + "/_matrix/client/r0/logout", server_response, std::move(additional_args), use_tor, true);
// Make sure all fields are reset here!
rooms.clear();
- room_list_read_index = 0;
room_data_by_id.clear();
user_id.clear();
username.clear();
@@ -2021,6 +2350,7 @@ namespace QuickMedia {
homeserver.clear();
upload_limit.reset();
next_batch.clear();
+ invites.clear();
return PluginResult::OK;
}
@@ -2119,9 +2449,10 @@ namespace QuickMedia {
std::string server_response;
// We want to make any request to the server that can verify that our token is still valid, doesn't matter which call
- if(download_to_string(homeserver + "/_matrix/client/r0/account/whoami", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) {
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/account/whoami", server_response, std::move(additional_args), use_tor, true);
+ if(download_result != DownloadResult::OK) {
fprintf(stderr, "Matrix whoami response: %s\n", server_response.c_str());
- return PluginResult::NET_ERR;
+ return download_result_to_plugin_result(download_result);
}
this->user_id = std::move(user_id);
@@ -2148,10 +2479,8 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id) , server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
-
- return PluginResult::OK;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id) , server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
}
PluginResult Matrix::on_stop_typing(RoomData *room) {
@@ -2170,10 +2499,8 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id), server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
-
- return PluginResult::OK;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id), server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
}
PluginResult Matrix::set_read_marker(RoomData *room, const Message *message) {
@@ -2194,15 +2521,42 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/read_markers", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/read_markers", server_response, std::move(additional_args), use_tor, true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
auto me = get_me(room);
if(me)
room->set_user_read_marker(me, message->event_id);
+
return PluginResult::OK;
}
+ PluginResult Matrix::join_room(const std::string &room_id) {
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", "{}" },
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string server_response;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/join/" + room_id, server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
+ }
+
+ PluginResult Matrix::leave_room(const std::string &room_id) {
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", "{}" },
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string server_response;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room_id + "/leave", server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
+ }
+
bool Matrix::was_message_posted_by_me(void *message) {
Message *message_typed = (Message*)message;
return user_id == message_typed->user->user_id;
@@ -2264,6 +2618,18 @@ namespace QuickMedia {
rooms.push_back(std::move(room));
}
+ void Matrix::remove_room(const std::string &room_id) {
+ std::lock_guard<std::mutex> lock(room_data_mutex);
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end())
+ return;
+
+ // We want to clear data instead of removing the object iself becasue we want to instance to still be valid,
+ // also in the future we can have a "history" tag for rooms we have just left
+ rooms[room_it->second]->clear_data();
+ room_data_by_id.erase(room_it);
+ }
+
DownloadResult Matrix::download_json(rapidjson::Document &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent, std::string *err_msg) const {
if(download_to_json(url, result, std::move(additional_args), use_tor, use_browser_useragent, err_msg == nullptr) != DownloadResult::OK) {
// Cant get error since we parse directory to json. TODO: Make this work somehow?