From 0df442e04c57dd80fc9a6b885b2ba86442b405b9 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 12 Nov 2020 02:35:19 +0100 Subject: Matrix: workaround synapse bug where sync doesn't include membership states when using messages filter --- TODO | 3 +- include/Body.hpp | 30 ++++---- plugins/Matrix.hpp | 12 +++ src/QuickMedia.cpp | 201 +++++++++++++++++++++++++++++++++++++++++++------ src/plugins/Matrix.cpp | 85 ++++++++++++++++----- 5 files changed, 271 insertions(+), 60 deletions(-) diff --git a/TODO b/TODO index 512a63f..50d1eae 100644 --- a/TODO +++ b/TODO @@ -127,5 +127,4 @@ Show a marker when a room uses encryption. Remove replied-to message text in room preview. That shows ignored users text and we want to see the reply message instead anyways. Update room name/avatar with new data in /sync. Read marker is incorrect if the last message is an edit/redact, because they are hidden and replaces other body items instead. -Scroll tabs if there are more than 3 tab items and show arrow on left/right side when there are more items to see. -/sync doesn't include user displayname/avatar for new messages (synapse bug?). Use /profile in those cases (merge with fetching message by id used for replies and pinned messages). (Mark messages user as not resolved). \ No newline at end of file +Scroll tabs if there are more than 3 tab items and show arrow on left/right side when there are more items to see. \ No newline at end of file diff --git a/include/Body.hpp b/include/Body.hpp index 1952b74..f4742bc 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -39,21 +39,21 @@ namespace QuickMedia { static std::shared_ptr create(std::string title) { return std::make_shared(std::move(title)); } void set_title(std::string new_title) { - if(title.empty() && new_title.empty()) + if(title == new_title) return; title = std::move(new_title); dirty = true; } void set_description(std::string new_description) { - if(description.empty() && new_description.empty()) + if(description == new_description) return; description = std::move(new_description); dirty_description = true; } void set_author(std::string new_author) { - if(author.empty() && new_author.empty()) + if(author == new_author) return; author = std::move(new_author); dirty_author = true; @@ -68,24 +68,24 @@ namespace QuickMedia { } void set_title_color(sf::Color new_color) { - if(new_color != title_color) { - title_color = new_color; - dirty = true; - } + if(new_color == title_color) + return; + title_color = new_color; + dirty = true; } void set_description_color(sf::Color new_color) { - if(new_color != description_color) { - description_color = new_color; - dirty_description = true; - } + if(new_color == description_color) + return; + description_color = new_color; + dirty_description = true; } void set_author_color(sf::Color new_color) { - if(new_color != author_color) { - author_color = new_color; - dirty_author = true; - } + if(new_color == author_color) + return; + author_color = new_color; + dirty_author = true; } const std::string& get_title() const { return title; } diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 9371900..25225d7 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -12,6 +12,12 @@ namespace QuickMedia { struct RoomData; + enum class UserResolveState { + NOT_RESOLVED, + RESOLVING, + RESOLVED + }; + struct UserInfo { friend struct RoomData; UserInfo(RoomData *room, std::string user_id); @@ -20,6 +26,7 @@ namespace QuickMedia { RoomData *room; const sf::Color display_name_color; const std::string user_id; + UserResolveState resolve_state; private: std::string display_name; std::string avatar_url; @@ -37,6 +44,8 @@ namespace QuickMedia { MEMBERSHIP }; + bool is_visual_media_message_type(MessageType message_type); + enum class RelatedEventType { NONE, REPLY, @@ -110,6 +119,7 @@ namespace QuickMedia { // These 4 variables are set by QuickMedia, not the matrix plugin bool last_message_read = true; + bool users_fetched = false; time_t last_read_message_timestamp = 0; void *userdata = nullptr; // Pointer to BodyItem. Note: this has to be valid as long as the room is valid @@ -464,6 +474,8 @@ namespace QuickMedia { std::shared_ptr get_message_by_id(RoomData *room, const std::string &event_id); RoomData* get_room_by_id(const std::string &id); + void update_user_with_latest_state(RoomData *room, const std::string &user_id); + void update_room_users(RoomData *room); bool use_tor = false; private: diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index f3e83f6..590eb86 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3116,7 +3116,7 @@ namespace QuickMedia { pinned_tab.body = std::make_unique(this, loading_icon); pinned_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; pinned_tab.body->thumbnail_mask_shader = &circle_mask_shader; - pinned_tab.body->attach_side = AttachSide::BOTTOM; + pinned_tab.body->attach_side = AttachSide::TOP; //pinned_tab.body->line_separator_color = sf::Color::Transparent; pinned_tab.text = sf::Text("Pinned messages", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); tabs.push_back(std::move(pinned_tab)); @@ -3130,8 +3130,18 @@ namespace QuickMedia { messages_tab.text = sf::Text("Messages", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); tabs.push_back(std::move(messages_tab)); + // ChatTab users_tab; + // users_tab.body = std::make_unique(this, loading_icon); + // users_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; + // users_tab.body->thumbnail_mask_shader = &circle_mask_shader; + // users_tab.body->attach_side = AttachSide::TOP; + // //users_tab.body->line_separator_color = sf::Color::Transparent; + // users_tab.text = sf::Text("Users", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); + // tabs.push_back(std::move(users_tab)); + const int PINNED_TAB_INDEX = 0; const int MESSAGES_TAB_INDEX = 1; + //const int USERS_TAB_INDEX = 2; int selected_tab = MESSAGES_TAB_INDEX; bool is_window_focused = window.hasFocus(); @@ -3258,6 +3268,7 @@ namespace QuickMedia { bool empty_before = tabs[PINNED_TAB_INDEX].body->items.empty(); int selected_before = tabs[PINNED_TAB_INDEX].body->get_selected_item(); auto prev_pinned_body_items = tabs[PINNED_TAB_INDEX].body->items; + tabs[PINNED_TAB_INDEX].body->clear_items(); // TODO: Add message to rooms messages when there are new pinned events for(const std::string &event : pinned_events.value()) { @@ -3409,23 +3420,53 @@ namespace QuickMedia { AsyncTask previous_messages_future; + enum class FetchMessageType { + MESSAGE, + USER_UPDATE, + ROOM_USERS + }; + + struct FetchMessageResult { + FetchMessageType type; + std::shared_ptr message; + }; + //const int num_fetch_message_threads = 4; - AsyncTask> fetch_message_future; - RoomData *fetch_future_room = nullptr; + AsyncTask fetch_message_future; + Message *fetch_message = nullptr; BodyItem *fetch_body_item = nullptr; int fetch_message_tab = -1; // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. // TODO: Cancel when going to another room? - tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &fetch_message, &find_body_item_by_event_id, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { if(fetch_message_future.valid() || !current_room) return; PinnedEventData *event_data = static_cast(body_item->userdata); - if(!event_data || event_data->status != FetchStatus::NONE) + if(!event_data) + return; + +#if 0 + if(event_data->message->user->resolve_state == UserResolveState::NOT_RESOLVED) { + fetch_message = event_data->message; + event_data->message->user->resolve_state = UserResolveState::RESOLVING; + std::string user_id = event_data->message->user->user_id; + fetch_message_future = [this, ¤t_room, user_id]() { + matrix->update_user_with_latest_state(current_room, user_id); + return FetchMessageResult{FetchMessageType::USER_UPDATE, nullptr}; + }; + return; + } else if(event_data->message->user->resolve_state == UserResolveState::RESOLVING) { + return; + } +#endif + + if(event_data->status != FetchStatus::NONE) return; - // Check if we already have the referenced message as a body item in the messages list, so we dont create a new one + // Check if we already have the referenced message as a body item in the messages list, so we dont create a new one. + // TODO: Optimize from linear search to hash map auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), event_data->event_id); if(related_body_item) { *body_item = *related_body_item; @@ -3436,28 +3477,44 @@ namespace QuickMedia { } std::string message_event_id = event_data->event_id; - fetch_future_room = current_room; fetch_body_item = body_item; event_data->status = FetchStatus::LOADING; fetch_message_tab = PINNED_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? - fetch_message_future = [this, &fetch_future_room, message_event_id]() { - return matrix->get_message_by_id(fetch_future_room, message_event_id); + fetch_message_future = [this, ¤t_room, message_event_id]() { + return FetchMessageResult{FetchMessageType::MESSAGE, matrix->get_message_by_id(current_room, message_event_id)}; }; }; // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. // TODO: Cancel when going to another room? - tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &fetch_message, &find_body_item_by_event_id, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { if(fetch_message_future.valid() || !current_room) return; Message *message = static_cast(body_item->userdata); assert(message); + +#if 0 + if(message->user->resolve_state == UserResolveState::NOT_RESOLVED) { + fetch_message = message; + message->user->resolve_state = UserResolveState::RESOLVING; + std::string user_id = message->user->user_id; + fetch_message_future = [this, ¤t_room, user_id]() { + matrix->update_user_with_latest_state(current_room, user_id); + return FetchMessageResult{FetchMessageType::USER_UPDATE, nullptr}; + }; + return; + } else if(message->user->resolve_state == UserResolveState::RESOLVING) { + return; + } +#endif + if(message->related_event_id.empty() || body_item->embedded_item_status != FetchStatus::NONE) return; - // Check if we already have the referenced message as a body item, so we dont create a new one + // Check if we already have the referenced message as a body item, so we dont create a new one. + // TODO: Optimize from linear search to hash map auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), message->related_event_id); if(related_body_item) { body_item->embedded_item = related_body_item; @@ -3466,13 +3523,12 @@ namespace QuickMedia { } std::string message_event_id = message->related_event_id; - fetch_future_room = current_room; fetch_body_item = body_item; body_item->embedded_item_status = FetchStatus::LOADING; fetch_message_tab = MESSAGES_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? - fetch_message_future = [this, &fetch_future_room, message_event_id]() { - return matrix->get_message_by_id(fetch_future_room, message_event_id); + fetch_message_future = [this, ¤t_room, message_event_id]() { + return FetchMessageResult{FetchMessageType::MESSAGE, matrix->get_message_by_id(current_room, message_event_id)}; }; }; @@ -3491,7 +3547,7 @@ namespace QuickMedia { sf::Vertex gradient_points[4]; double gradient_inc = 0; - tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y); + //tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y); bool fetched_enough_messages = false; bool initial_prev_messages_fetch = true; @@ -3648,6 +3704,81 @@ namespace QuickMedia { return false; }; + auto update_pinned_messages_author = [&tabs, ¤t_room](const std::shared_ptr &user) { + fprintf(stderr, "updated pinned messages author for user: %s\n", user->user_id.c_str()); + std::string user_display_name = current_room->get_user_display_name(user); + std::string user_avatar_url = current_room->get_user_avatar_url(user); + + for(auto &pinned_body_item : tabs[PINNED_TAB_INDEX].body->items) { + Message *message = static_cast(pinned_body_item->userdata)->message; + // Its fine if we dont set it now. When the message is fetches, it will have updated user info since its fetched later + if(!message || message->user != user) + continue; + + pinned_body_item->set_author(user_display_name); + if(!is_visual_media_message_type(message->type)) { + pinned_body_item->thumbnail_url = user_avatar_url; + pinned_body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that + pinned_body_item->thumbnail_size = sf::Vector2i(32, 32); + } + } + }; + + auto update_messages_author = [&tabs, ¤t_room](const std::shared_ptr &user) { + fprintf(stderr, "updated messages author for user: %s\n", user->user_id.c_str()); + std::string user_display_name = current_room->get_user_display_name(user); + std::string user_avatar_url = current_room->get_user_avatar_url(user); + + for(auto &message_body_items : tabs[MESSAGES_TAB_INDEX].body->items) { + Message *message = static_cast(message_body_items->userdata); + if(message->user != user) + continue; + + message_body_items->set_author(user_display_name); + if(!is_visual_media_message_type(message->type)) { + message_body_items->thumbnail_url = user_avatar_url; + message_body_items->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that + message_body_items->thumbnail_size = sf::Vector2i(32, 32); + } + } + }; + + // TODO: Optimize + auto update_pinned_messages_authors = [&tabs, ¤t_room]() { + fprintf(stderr, "updated pinned messages author for all users\n"); + for(auto &pinned_body_item : tabs[PINNED_TAB_INDEX].body->items) { + Message *message = static_cast(pinned_body_item->userdata)->message; + // Its fine if we dont set it now. When the message is fetches, it will have updated user info since its fetched later + if(!message) + continue; + + pinned_body_item->set_author(current_room->get_user_display_name(message->user)); + if(!is_visual_media_message_type(message->type)) { + pinned_body_item->thumbnail_url = current_room->get_user_avatar_url(message->user); + pinned_body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that + pinned_body_item->thumbnail_size = sf::Vector2i(32, 32); + } + } + }; + + // TODO: Optimize + auto update_messages_authors = [&tabs, ¤t_room]() { + fprintf(stderr, "updated messages author for all users\n"); + for(auto &message_body_items : tabs[MESSAGES_TAB_INDEX].body->items) { + Message *message = static_cast(message_body_items->userdata); + message_body_items->set_author(current_room->get_user_display_name(message->user)); + if(!is_visual_media_message_type(message->type)) { + message_body_items->thumbnail_url = current_room->get_user_avatar_url(message->user); + message_body_items->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that + message_body_items->thumbnail_size = sf::Vector2i(32, 32); + } + } + }; + auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &post_thread, &tabs]() { set_read_marker_future.cancel(); fetch_message_future.cancel(); @@ -3674,6 +3805,19 @@ namespace QuickMedia { //tabs.clear(); }; + // TODO: Remove this once synapse bug has been resolved where /sync does not include user info for new messages when using message filter that limits number of messages for initial sync, + // and then only call this when viewing the users tab for the first time. + if(current_room->users_fetched) { + //TODO BLABLA + //update_ + } else { + // TODO: Race condition? maybe use matrix /members instead which has a since parameter to make the members list match current sync + fetch_message_future = [this, ¤t_room]() { + matrix->update_room_users(current_room); + return FetchMessageResult{FetchMessageType::ROOM_USERS, nullptr}; + }; + } + float tab_shade_height = 0.0f; bool frame_skip_text_entry = false; @@ -4108,31 +4252,38 @@ namespace QuickMedia { // XXX: Hack to scroll up while keeping the selected item (usually the last one) visible if(move_to_bottom) { tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y); + //tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y); } } fetch_more_previous_messages_if_needed(); } if(fetch_message_future.ready()) { - std::shared_ptr message = fetch_message_future.get(); - fprintf(stderr, "Finished fetching message: %s\n", message ? message->event_id.c_str() : "(null)"); - // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again - if(fetch_future_room == current_room) { + FetchMessageResult fetch_message_result = fetch_message_future.get(); + if(fetch_message_result.type == FetchMessageType::ROOM_USERS) { + current_room->users_fetched = true; + update_pinned_messages_authors(); + update_messages_authors(); + } else if(fetch_message_result.type == FetchMessageType::USER_UPDATE) { + update_pinned_messages_author(fetch_message->user); + update_messages_author(fetch_message->user); + fetch_message = nullptr; + } else if(fetch_message_result.type == FetchMessageType::MESSAGE) { + fprintf(stderr, "Finished fetching message: %s\n", fetch_message_result.message ? fetch_message_result.message->event_id.c_str() : "(null)"); if(fetch_message_tab == PINNED_TAB_INDEX) { PinnedEventData *event_data = static_cast(fetch_body_item->userdata); - if(message) { - *fetch_body_item = *message_to_body_item(current_room, message.get(), current_room->get_user_display_name(me), me->user_id); + if(fetch_message_result.message) { + *fetch_body_item = *message_to_body_item(current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id); event_data->status = FetchStatus::FINISHED_LOADING; - event_data->message = message.get(); + event_data->message = fetch_message_result.message.get(); fetch_body_item->userdata = event_data; } else { fetch_body_item->set_description("Failed to load message!"); event_data->status = FetchStatus::FAILED_TO_LOAD; } } else if(fetch_message_tab == MESSAGES_TAB_INDEX) { - if(message) { - fetch_body_item->embedded_item = message_to_body_item(current_room, message.get(), current_room->get_user_display_name(me), me->user_id); + if(fetch_message_result.message) { + fetch_body_item->embedded_item = message_to_body_item(current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id); fetch_body_item->embedded_item_status = FetchStatus::FINISHED_LOADING; } else { fetch_body_item->embedded_item_status = FetchStatus::FAILED_TO_LOAD; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index ce003a0..a48bcdd 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -99,16 +99,21 @@ namespace QuickMedia { } UserInfo::UserInfo(RoomData *room, std::string user_id) : - room(room), display_name_color(user_id_to_color(user_id)), user_id(user_id) + room(room), display_name_color(user_id_to_color(user_id)), user_id(user_id), resolve_state(UserResolveState::NOT_RESOLVED) { display_name = std::move(user_id); } UserInfo::UserInfo(RoomData *room, std::string user_id, std::string display_name, std::string avatar_url) : - room(room), display_name_color(user_id_to_color(user_id)), user_id(std::move(user_id)), display_name(std::move(display_name)), avatar_url(std::move(avatar_url)) { + room(room), display_name_color(user_id_to_color(user_id)), user_id(std::move(user_id)), resolve_state(UserResolveState::RESOLVED), display_name(std::move(display_name)), avatar_url(std::move(avatar_url)) { } + // TODO: Remove this when images are embedded inside the text instead of using the same space as the author + bool is_visual_media_message_type(MessageType message_type) { + return message_type == MessageType::VIDEO || message_type == MessageType::IMAGE; + } + std::shared_ptr RoomData::get_user_by_id(const std::string &user_id) { std::lock_guard lock(room_mutex); auto user_it = user_info_by_user_id.find(user_id); @@ -147,11 +152,13 @@ namespace QuickMedia { user->display_name = std::move(display_name); if(user->display_name.empty()) user->display_name = user->user_id; + user->resolve_state = UserResolveState::RESOLVED; } void RoomData::set_user_avatar_url(std::shared_ptr &user, std::string avatar_url) { std::lock_guard lock(user_mutex); user->avatar_url = std::move(avatar_url); + user->resolve_state = UserResolveState::RESOLVED; } void RoomData::prepend_messages_reverse(const std::vector> &new_messages) { @@ -1477,7 +1484,7 @@ namespace QuickMedia { std::string avatar_url_str; const rapidjson::Value &avatar_url_json = GetMember(json, "avatar_url"); if(avatar_url_json.IsString()) - avatar_url_str = avatar_url_json.GetString(); + avatar_url_str = std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength()); const rapidjson::Value &display_name_json = GetMember(json, "displayname"); @@ -1485,9 +1492,12 @@ namespace QuickMedia { std::string avatar_url = thumbnail_url_extract_media_id(avatar_url_str); if(!avatar_url.empty()) avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + avatar_url + "?width=32&height=32&method=crop"; // TODO: Remove the constant strings around to reduce memory usage (6.3mb) - auto user_info = std::make_shared(room_data, user_id, std::move(display_name), std::move(avatar_url)); + //auto user_info = std::make_shared(room_data, user_id, std::move(display_name), std::move(avatar_url)); // Overwrites user data - room_data->add_user(user_info); + //room_data->add_user(user_info); + auto user_info = get_user_by_id(room_data, user_id); + room_data->set_user_display_name(user_info, std::move(display_name)); + room_data->set_user_avatar_url(user_info, std::move(avatar_url)); return user_info; } @@ -3289,9 +3299,14 @@ namespace QuickMedia { auto user = room->get_user_by_id(user_id); if(user) return user; - #if 0 - // TODO: Instead of guessing notification limit with 100, accumulate rooms unread_notifications count and use that as the limit - // (and take into account that notification response may have notifications after call to sync above). + + //fprintf(stderr, "Unknown user: %s, creating locally... synapse bug?\n", user_id.c_str()); + auto user_info = std::make_shared(room, user_id); + room->add_user(user_info); + return user_info; + } + + void Matrix::update_user_with_latest_state(RoomData *room, const std::string &user_id) { char url[512]; snprintf(url, sizeof(url), "%s/_matrix/client/r0/profile/%s", homeserver.c_str(), user_id.c_str()); @@ -3299,18 +3314,52 @@ namespace QuickMedia { DownloadResult download_result = download_json(json_root, url, {}, true); if(download_result != DownloadResult::OK || !json_root.IsObject()) { fprintf(stderr, "Fetching profile for user %s failed!\n", user_id.c_str()); - return nullptr; + auto user = get_user_by_id(room, user_id); + assert(user); + user->resolve_state = UserResolveState::RESOLVED; + return; } - // Is this a synapse bug? sometimes lazy_fetch_members doesn't contain all related clients - fprintf(stderr, "User was not available locally, fetched from server...\n"); - return parse_user_info(json_root, user_id, room); - #else - fprintf(stderr, "Unknown user: %s, creating locally... synapse bug?\n", user_id.c_str()); - auto user_info = std::make_shared(room, user_id); - room->add_user(user_info); - return user_info; - #endif + parse_user_info(json_root, user_id, room); + } + + void Matrix::update_room_users(RoomData *room) { + std::vector additional_args = { + { "-H", "Authorization: Bearer " + access_token } + }; + + char url[512]; + snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/joined_members", homeserver.c_str(), room->id.c_str()); + + rapidjson::Document json_root; + DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); + if(download_result != DownloadResult::OK || !json_root.IsObject()) { + fprintf(stderr, "Fetching users for room %s failed!\n", room->id.c_str()); + return; + } + + const rapidjson::Value &joined_json = GetMember(json_root, "joined"); + if(!joined_json.IsObject()) + return; + + for(auto const &joined_obj : joined_json.GetObject()) { + if(!joined_obj.name.IsString() || !joined_obj.value.IsObject()) + continue; + + const rapidjson::Value &avatar_url_json = GetMember(joined_obj.value, "avatar_url"); + const rapidjson::Value &display_name_json = GetMember(joined_obj.value, "display_name"); + auto user = get_user_by_id(room, std::string(joined_obj.name.GetString(), joined_obj.name.GetStringLength())); + assert(user); + + std::string display_name = display_name_json.IsString() ? display_name_json.GetString() : user_id; + std::string avatar_url; + if(avatar_url_json.IsString()) + avatar_url = std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength()); + if(!avatar_url.empty()) + avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + thumbnail_url_extract_media_id(avatar_url) + "?width=32&height=32&method=crop"; // TODO: Remove the constant strings around to reduce memory usage (6.3mb) + room->set_user_avatar_url(user, std::move(avatar_url)); + room->set_user_display_name(user, std::move(display_name)); + } } // TODO: GET the filter to check if its valid? -- cgit v1.2.3