aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Matrix.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-10-17 09:40:10 +0200
committerdec05eba <dec05eba@protonmail.com>2020-10-17 10:49:13 +0200
commit9bf163d51a252fb5a611e88c2e0b4123a98169e1 (patch)
treee742979a7128e3ead913e9348c4b207c0fd95ab4 /src/plugins/Matrix.cpp
parentfd9178b9d500a0b5f30f388f8d419ac386ce87cb (diff)
Matrix: show reply messages embedded in messages that reply to them, like element does
Diffstat (limited to 'src/plugins/Matrix.cpp')
-rw-r--r--src/plugins/Matrix.cpp399
1 files changed, 189 insertions, 210 deletions
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 <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <fcntl.h>
@@ -447,173 +448,181 @@ namespace QuickMedia {
std::vector<std::shared_ptr<Message>> new_messages;
for(const rapidjson::Value &event_item_json : events_json.GetArray()) {
- if(!event_item_json.IsObject())
+ std::shared_ptr<Message> 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<Message> 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>();
- 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>();
+ 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<Message>();
- 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<Message>();
+ 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 "<mx-reply>"
"<blockquote>"
"<a href=\"https://invalid.url\">In reply to</a>"
@@ -1125,88 +1138,54 @@ namespace QuickMedia {
// TODO: Right now this recursively calls /rooms/<room_id>/context/<event_id> 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<Message> Matrix::get_edited_message_original_message(RoomData *room_data, std::shared_ptr<Message> 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<rapidjson::StringBuffer> writer(buffer);
- request_data.Accept(writer);
-
- std::vector<CommandArg> 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<Message> Matrix::get_message_by_id(RoomData *room, const std::string &event_id) {
+ std::shared_ptr<Message> 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<rapidjson::StringBuffer> writer(buffer);
+ request_data.Accept(writer);
- const rapidjson::Value &content_json = GetMember(event_json, "content");
- if(!content_json.IsObject())
- return nullptr;
+ std::vector<CommandArg> 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<Message>();
- 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<Message> 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