From 9bf163d51a252fb5a611e88c2e0b4123a98169e1 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 17 Oct 2020 09:40:10 +0200 Subject: Matrix: show reply messages embedded in messages that reply to them, like element does --- src/plugins/Matrix.cpp | 399 +++++++++++++++++++++++-------------------------- 1 file changed, 189 insertions(+), 210 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 0866bac..1cb2aa5 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -1,6 +1,7 @@ #include "../../plugins/Matrix.hpp" #include "../../include/Storage.hpp" #include "../../include/StringUtils.hpp" +#include #include #include #include @@ -447,173 +448,181 @@ namespace QuickMedia { std::vector> new_messages; for(const rapidjson::Value &event_item_json : events_json.GetArray()) { - if(!event_item_json.IsObject()) + std::shared_ptr new_message = parse_message_event(event_item_json, room_data.get()); + if(!new_message) continue; - const rapidjson::Value &sender_json = GetMember(event_item_json, "sender"); - if(!sender_json.IsString()) - continue; + // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions) + if(has_unread_notifications && !username.empty()) + new_message->mentions_me = message_contains_user_mention(new_message->body, username) || message_contains_user_mention(new_message->body, "@room"); - std::string sender_json_str = sender_json.GetString(); + new_messages.push_back(std::move(new_message)); + } - const rapidjson::Value &event_id_json = GetMember(event_item_json, "event_id"); - if(!event_id_json.IsString()) - continue; + // TODO: Add directly to this instead when set? otherwise add to new_messages + if(room_messages) + (*room_messages)[room_data] = new_messages; - std::string event_id_str = event_id_json.GetString(); - - const rapidjson::Value *content_json = &GetMember(event_item_json, "content"); - if(!content_json->IsObject()) - continue; + // TODO: Loop and std::move instead? doesn't insert create copies? + if(message_dir == MessageDirection::BEFORE) { + room_data->prepend_messages_reverse(std::move(new_messages)); + } else if(message_dir == MessageDirection::AFTER) { + room_data->append_messages(std::move(new_messages)); + } + } - auto user = room_data->get_user_by_id(sender_json_str); - if(!user) { - // 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; - } + std::shared_ptr Matrix::parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data) { + if(!event_item_json.IsObject()) + return nullptr; - time_t timestamp = 0; - const rapidjson::Value &origin_server_ts = GetMember(event_item_json, "origin_server_ts"); - if(origin_server_ts.IsNumber()) - timestamp = origin_server_ts.GetInt64(); + const rapidjson::Value &sender_json = GetMember(event_item_json, "sender"); + if(!sender_json.IsString()) + return nullptr; - const rapidjson::Value &type_json = GetMember(event_item_json, "type"); - if(!type_json.IsString()) - continue; + std::string sender_json_str = sender_json.GetString(); - RelatedEventType related_event_type = RelatedEventType::NONE; - std::string related_event_id; - const rapidjson::Value &relates_to_json = GetMember(*content_json, "m.relates_to"); - if(relates_to_json.IsObject()) { - const rapidjson::Value &replaces_event_id_json = GetMember(relates_to_json, "event_id"); - const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); - if(replaces_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) { - related_event_id = replaces_event_id_json.GetString(); - related_event_type = RelatedEventType::EDIT; - } else { - const rapidjson::Value &in_reply_to_json = GetMember(relates_to_json, "m.in_reply_to"); - if(in_reply_to_json.IsObject()) { - const rapidjson::Value &in_reply_to_event_id = GetMember(in_reply_to_json, "event_id"); - if(in_reply_to_event_id.IsString()) { - related_event_id = in_reply_to_event_id.GetString(); - related_event_type = RelatedEventType::REPLY; - } + const rapidjson::Value &event_id_json = GetMember(event_item_json, "event_id"); + if(!event_id_json.IsString()) + return nullptr; + + std::string event_id_str = event_id_json.GetString(); + + const rapidjson::Value *content_json = &GetMember(event_item_json, "content"); + if(!content_json->IsObject()) + return nullptr; + + auto user = room_data->get_user_by_id(sender_json_str); + if(!user) { + // Note: this is important because otherwise replying and such is broken + fprintf(stderr, "Warning: skipping unknown user: %s\n", sender_json_str.c_str()); + return nullptr; + } + + time_t timestamp = 0; + const rapidjson::Value &origin_server_ts = GetMember(event_item_json, "origin_server_ts"); + if(origin_server_ts.IsNumber()) + timestamp = origin_server_ts.GetInt64(); + + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString()) + return nullptr; + + RelatedEventType related_event_type = RelatedEventType::NONE; + std::string related_event_id; + const rapidjson::Value &relates_to_json = GetMember(*content_json, "m.relates_to"); + if(relates_to_json.IsObject()) { + const rapidjson::Value &replaces_event_id_json = GetMember(relates_to_json, "event_id"); + const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); + if(replaces_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) { + related_event_id = replaces_event_id_json.GetString(); + related_event_type = RelatedEventType::EDIT; + } else { + const rapidjson::Value &in_reply_to_json = GetMember(relates_to_json, "m.in_reply_to"); + if(in_reply_to_json.IsObject()) { + const rapidjson::Value &in_reply_to_event_id = GetMember(in_reply_to_json, "event_id"); + if(in_reply_to_event_id.IsString()) { + related_event_id = in_reply_to_event_id.GetString(); + related_event_type = RelatedEventType::REPLY; } } } + } - const rapidjson::Value &new_content_json = GetMember(*content_json, "m.new_content"); - if(new_content_json.IsObject()) - content_json = &new_content_json; - - const rapidjson::Value &content_type = GetMember(*content_json, "msgtype"); - if(!content_type.IsString() || strcmp(type_json.GetString(), "m.room.redaction") == 0) { - auto message = std::make_shared(); - message->type = MessageType::REDACTION; - message->user = user; - message->event_id = event_id_str; - message->body = "Message deleted"; - message->timestamp = timestamp; - message->related_event_type = RelatedEventType::REDACTION; - - const rapidjson::Value &reason_json = GetMember(*content_json, "reason"); - if(reason_json.IsString()) { - message->body += ", reason: "; - message->body += reason_json.GetString(); - } + const rapidjson::Value &new_content_json = GetMember(*content_json, "m.new_content"); + if(new_content_json.IsObject()) + content_json = &new_content_json; - const rapidjson::Value &redacts_json = GetMember(event_item_json, "redacts"); - if(redacts_json.IsString()) - message->related_event_id = redacts_json.GetString(); + const rapidjson::Value &content_type = GetMember(*content_json, "msgtype"); + if(!content_type.IsString() || strcmp(type_json.GetString(), "m.room.redaction") == 0) { + auto message = std::make_shared(); + message->type = MessageType::REDACTION; + message->user = user; + message->event_id = event_id_str; + message->body = "Message deleted"; + message->timestamp = timestamp; + message->related_event_type = RelatedEventType::REDACTION; - new_messages.push_back(message); - continue; + const rapidjson::Value &reason_json = GetMember(*content_json, "reason"); + if(reason_json.IsString()) { + message->body += ", reason: "; + message->body += reason_json.GetString(); } - if(strcmp(type_json.GetString(), "m.room.message") != 0) - continue; - - const rapidjson::Value &body_json = GetMember(*content_json, "body"); - if(!body_json.IsString()) - continue; + const rapidjson::Value &redacts_json = GetMember(event_item_json, "redacts"); + if(redacts_json.IsString()) + message->related_event_id = redacts_json.GetString(); - auto message = std::make_shared(); - std::string prefix; + return message; + } - // TODO: Also show joins, leave, invites, bans, kicks, mutes, etc + if(strcmp(type_json.GetString(), "m.room.message") != 0) + return nullptr; - if(strcmp(content_type.GetString(), "m.text") == 0) { - message->type = MessageType::TEXT; - } else if(strcmp(content_type.GetString(), "m.image") == 0) { - const rapidjson::Value &url_json = GetMember(*content_json, "url"); - if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) - continue; + const rapidjson::Value &body_json = GetMember(*content_json, "body"); + if(!body_json.IsString()) + return nullptr; - message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); - message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); - message->type = MessageType::IMAGE; - } else if(strcmp(content_type.GetString(), "m.video") == 0) { - const rapidjson::Value &url_json = GetMember(*content_json, "url"); - if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) - continue; + auto message = std::make_shared(); + std::string prefix; - message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); - message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); - message->type = MessageType::VIDEO; - } else if(strcmp(content_type.GetString(), "m.audio") == 0) { - const rapidjson::Value &url_json = GetMember(*content_json, "url"); - if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) - continue; + // TODO: Also show joins, leave, invites, bans, kicks, mutes, etc - message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); - message->type = MessageType::AUDIO; - } else if(strcmp(content_type.GetString(), "m.file") == 0) { - const rapidjson::Value &url_json = GetMember(*content_json, "url"); - if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) - continue; + if(strcmp(content_type.GetString(), "m.text") == 0) { + message->type = MessageType::TEXT; + } else if(strcmp(content_type.GetString(), "m.image") == 0) { + const rapidjson::Value &url_json = GetMember(*content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) + return nullptr; - message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); - message->type = MessageType::FILE; - } else if(strcmp(content_type.GetString(), "m.emote") == 0) { // this is a /me message, TODO: show /me messages differently - message->type = MessageType::TEXT; - prefix = "*" + user->display_name + "* "; - } else if(strcmp(content_type.GetString(), "m.notice") == 0) { // TODO: show notices differently - message->type = MessageType::TEXT; - prefix = "* NOTICE * "; - } else if(strcmp(content_type.GetString(), "m.location") == 0) { // TODO: show locations differently - const rapidjson::Value &geo_uri_json = GetMember(*content_json, "geo_uri"); - if(geo_uri_json.IsString()) - prefix = geo_uri_json.GetString() + std::string(" | "); - - message->type = MessageType::TEXT; - message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); - } else { - continue; - } + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); + message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); + message->type = MessageType::IMAGE; + } else if(strcmp(content_type.GetString(), "m.video") == 0) { + const rapidjson::Value &url_json = GetMember(*content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) + return nullptr; - message->user = user; - message->event_id = event_id_str; - message->body = prefix + body_json.GetString(); - message->related_event_id = std::move(related_event_id); - message->related_event_type = related_event_type; - // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions) - if(has_unread_notifications && !username.empty()) - message->mentions_me = message_contains_user_mention(message->body, username) || message_contains_user_mention(message->body, "@room"); - message->timestamp = timestamp; - new_messages.push_back(message); - } + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); + message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); + message->type = MessageType::VIDEO; + } else if(strcmp(content_type.GetString(), "m.audio") == 0) { + const rapidjson::Value &url_json = GetMember(*content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) + return nullptr; - // TODO: Add directly to this instead when set? otherwise add to new_messages - if(room_messages) - (*room_messages)[room_data] = new_messages; + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); + message->type = MessageType::AUDIO; + } else if(strcmp(content_type.GetString(), "m.file") == 0) { + const rapidjson::Value &url_json = GetMember(*content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) + return nullptr; - // TODO: Loop and std::move instead? doesn't insert create copies? - if(message_dir == MessageDirection::BEFORE) { - room_data->prepend_messages_reverse(std::move(new_messages)); - } else if(message_dir == MessageDirection::AFTER) { - room_data->append_messages(std::move(new_messages)); + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); + message->type = MessageType::FILE; + } else if(strcmp(content_type.GetString(), "m.emote") == 0) { // this is a /me message, TODO: show /me messages differently + message->type = MessageType::TEXT; + prefix = "*" + user->display_name + "* "; + } else if(strcmp(content_type.GetString(), "m.notice") == 0) { // TODO: show notices differently + message->type = MessageType::TEXT; + prefix = "* NOTICE * "; + } else if(strcmp(content_type.GetString(), "m.location") == 0) { // TODO: show locations differently + const rapidjson::Value &geo_uri_json = GetMember(*content_json, "geo_uri"); + if(geo_uri_json.IsString()) + prefix = geo_uri_json.GetString() + std::string(" | "); + + message->type = MessageType::TEXT; + message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); + } else { + return nullptr; } + + message->user = user; + message->event_id = event_id_str; + message->body = prefix + body_json.GetString(); + message->related_event_id = std::move(related_event_id); + message->related_event_type = related_event_type; + message->timestamp = timestamp; + return message; } // Returns empty string on error @@ -851,7 +860,7 @@ namespace QuickMedia { } if(file_info->duration_seconds) { // TODO: Check for overflow? - info_json.AddMember("duration", (int)file_info->duration_seconds.value() * 1000, request_data.GetAllocator()); + info_json.AddMember("duration", (int)(file_info->duration_seconds.value() * 1000.0), request_data.GetAllocator()); } if(thumbnail_info) { @@ -932,6 +941,8 @@ namespace QuickMedia { case MessageType::TEXT: { if(message->related_event_type != RelatedEventType::NONE) related_to_body = remove_reply_formatting(message->body); + else + related_to_body = message->body; break; } case MessageType::IMAGE: @@ -962,6 +973,8 @@ namespace QuickMedia { std::string related_to_body = get_reply_message(message); html_escape_sequences(formatted_body); html_escape_sequences(related_to_body); + // TODO: Fix invalid.url, etc to use same as element. This is required to navigate to reply message in element mobile. + // TODO: Add keybind to navigate to the reply message, which would also depend on this formatting. return "" "
" "In reply to" @@ -1125,88 +1138,54 @@ namespace QuickMedia { // TODO: Right now this recursively calls /rooms//context/ and trusts server to not make it recursive. To make this robust, check iteration count and do not trust server. // TODO: Optimize? std::shared_ptr Matrix::get_edited_message_original_message(RoomData *room_data, std::shared_ptr message) { - if(message->related_event_type != RelatedEventType::EDIT) + if(!message || message->related_event_type != RelatedEventType::EDIT) return message; + return get_edited_message_original_message(room_data, get_message_by_id(room_data, message->related_event_id)); + } - auto replaced_message = room_data->get_message_by_id(message->related_event_id); - if(!replaced_message) { - rapidjson::Document request_data(rapidjson::kObjectType); - request_data.AddMember("lazy_load_members", true, request_data.GetAllocator()); - - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - request_data.Accept(writer); - - std::vector additional_args = { - { "-H", "Authorization: Bearer " + access_token } - }; - - std::string filter = url_param_encode(buffer.GetString()); - - char url[512]; - snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/context/%s?limit=0&filter=%s", homeserver.c_str(), room_data->id.c_str(), message->event_id.c_str(), filter.c_str()); - - rapidjson::Document json_root; - DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); - if(download_result != DownloadResult::OK) return nullptr; - - if(!json_root.IsObject()) - return nullptr; + std::shared_ptr Matrix::get_message_by_id(RoomData *room, const std::string &event_id) { + std::shared_ptr existing_room_message = room->get_message_by_id(event_id); + if(existing_room_message) + return existing_room_message; - const rapidjson::Value &event_json = GetMember(json_root, "event"); - if(!event_json.IsObject()) - return nullptr; + auto fetched_message_it = room->fetched_messages_by_event_id.find(event_id); + if(fetched_message_it != room->fetched_messages_by_event_id.end()) + return fetched_message_it->second; + + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("lazy_load_members", true, request_data.GetAllocator()); - const rapidjson::Value &event_id_json = GetMember(event_json, "event_id"); - if(!event_id_json.IsString()) - return nullptr; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); - const rapidjson::Value &content_json = GetMember(event_json, "content"); - if(!content_json.IsObject()) - return nullptr; + std::vector additional_args = { + { "-H", "Authorization: Bearer " + access_token } + }; - const rapidjson::Value &body_json = GetMember(content_json, "body"); - if(!body_json.IsString()) - return nullptr; + std::string filter = url_param_encode(buffer.GetString()); - RelatedEventType related_event_type = RelatedEventType::NONE; - std::string related_event_id; - const rapidjson::Value &relates_to_json = GetMember(content_json, "m.relates_to"); - if(relates_to_json.IsObject()) { - const rapidjson::Value &event_id_json = GetMember(relates_to_json, "event_id"); - const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); - if(event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) { - related_event_id = event_id_json.GetString(); - related_event_type = RelatedEventType::EDIT; - } - } + char url[512]; + snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/context/%s?limit=0&filter=%s", homeserver.c_str(), room->id.c_str(), event_id.c_str(), filter.c_str()); - const rapidjson::Value &content_type = GetMember(content_json, "msgtype"); - if(!content_type.IsString()) - return nullptr; + std::string err_msg; + rapidjson::Document json_root; + DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg); + if(download_result != DownloadResult::OK) return nullptr; - auto new_message = std::make_shared(); - new_message->event_id = event_id_json.GetString(); - new_message->related_event_id = std::move(related_event_id); - new_message->related_event_type = related_event_type; - if(strcmp(content_type.GetString(), "m.text") == 0) { - new_message->type = MessageType::TEXT; - } else if(strcmp(content_type.GetString(), "m.image") == 0) { - new_message->type = MessageType::IMAGE; - } else if(strcmp(content_type.GetString(), "m.video") == 0) { - new_message->type = MessageType::VIDEO; - } else if(strcmp(content_type.GetString(), "m.audio") == 0) { - new_message->type = MessageType::AUDIO; - } else if(strcmp(content_type.GetString(), "m.file") == 0) { - new_message->type = MessageType::FILE; - } else { + if(json_root.IsObject()) { + const rapidjson::Value &error_json = GetMember(json_root, "error"); + if(error_json.IsString()) { + fprintf(stderr, "Matrix::get_message_by_id, error: %s\n", error_json.GetString()); + room->fetched_messages_by_event_id.insert(std::make_pair(event_id, nullptr)); return nullptr; } - - return get_edited_message_original_message(room_data, std::move(new_message)); - } else { - return get_edited_message_original_message(room_data, replaced_message); } + + const rapidjson::Value &event_json = GetMember(json_root, "event"); + std::shared_ptr new_message = parse_message_event(event_json, room); + room->fetched_messages_by_event_id.insert(std::make_pair(event_id, new_message)); + return new_message; } // Returns empty string on error -- cgit v1.2.3