aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-09-30 22:05:41 +0200
committerdec05eba <dec05eba@protonmail.com>2020-09-30 22:05:41 +0200
commit9602603135f456d906192112288dcd84429c8fee (patch)
tree6a6dafff82f3e38b8e18b74f474ef61965917339 /src/plugins
parente1a8d10b61c5f8ca092ba3aa458b661da29ba447 (diff)
Matrix: implement message editing
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Matrix.cpp271
1 files changed, 242 insertions, 29 deletions
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 50be2de..b93164e 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -140,6 +140,7 @@ namespace QuickMedia {
auto room_it = room_data_by_id.find(room_id_str);
if(room_it == room_data_by_id.end()) {
auto room_data = std::make_unique<RoomData>();
+ room_data->id = room_id_json.asString();
room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data)));
room_name = room_id_str;
fprintf(stderr, "Missing room %s from /sync, adding in joined_rooms\n", room_id_str.c_str());
@@ -268,6 +269,7 @@ namespace QuickMedia {
auto room_it = room_data_by_id.find(room_id_str);
if(room_it == room_data_by_id.end()) {
auto room_data = std::make_unique<RoomData>();
+ room_data->id = room_id_str;
room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data)));
room_it = room_data_by_id.find(room_id_str); // TODO: Get iterator from above insert
}
@@ -294,6 +296,7 @@ namespace QuickMedia {
auto room_it = room_data_by_id.find(room_id_str);
if(room_it == room_data_by_id.end()) {
auto room_data = std::make_unique<RoomData>();
+ room_data->id = room_id_str;
room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data)));
room_it = room_data_by_id.find(room_id_str); // TODO: Get iterator from above insert
}
@@ -442,53 +445,45 @@ namespace QuickMedia {
if(!body_json.isString())
continue;
- bool is_reply = false;
+ std::string replaces_event_id;
const Json::Value &relates_to_json = content_json["m.relates_to"];
if(relates_to_json.isObject()) {
- const Json::Value &in_reply_to_json = relates_to_json["m.in_reply_to"];
- is_reply = in_reply_to_json.isObject();
+ const Json::Value &replaces_event_id_json = relates_to_json["event_id"];
+ const Json::Value &rel_type_json = relates_to_json["rel_type"];
+ if(replaces_event_id_json.isString() && rel_type_json.isString() && strcmp(rel_type_json.asCString(), "m.replace") == 0)
+ replaces_event_id = replaces_event_id_json.asString();
}
+ auto message = std::make_shared<Message>();
+
if(strcmp(content_type.asCString(), "m.text") == 0) {
- auto message = std::make_shared<Message>();
- message->user_id = user_it->second;
- message->event_id = event_id_str;
- message->body = body_json.asString();
message->type = MessageType::TEXT;
- message->is_reply = is_reply;
- new_messages.push_back(message);
- room_data->message_by_event_id[event_id_str] = message;
} else if(strcmp(content_type.asCString(), "m.image") == 0) {
const Json::Value &url_json = content_json["url"];
if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0)
continue;
- auto message = std::make_shared<Message>();
- message->user_id = user_it->second;
- message->event_id = event_id_str;
- message->body = body_json.asString();
message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6);
message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver);
message->type = MessageType::IMAGE;
- message->is_reply = is_reply;
- new_messages.push_back(message);
- room_data->message_by_event_id[event_id_str] = message;
} else if(strcmp(content_type.asCString(), "m.video") == 0) {
const Json::Value &url_json = content_json["url"];
if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0)
continue;
- auto message = std::make_shared<Message>();
- message->event_id = event_id_str;
- message->user_id = user_it->second;
- message->body = body_json.asString();
message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6);
message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver);
message->type = MessageType::VIDEO;
- message->is_reply = is_reply;
- new_messages.push_back(message);
- room_data->message_by_event_id[event_id_str] = message;
+ } else {
+ continue;
}
+
+ message->user_id = user_it->second;
+ message->event_id = event_id_str;
+ message->body = body_json.asString();
+ message->replaces_event_id = std::move(replaces_event_id);
+ new_messages.push_back(message);
+ room_data->message_by_event_id[event_id_str] = message;
}
// TODO: Loop and std::move instead? doesn't insert create copies?
@@ -774,9 +769,13 @@ namespace QuickMedia {
static std::string create_body_for_message_reply(const RoomData *room_data, const Message *message, const std::string &body) {
std::string related_to_body;
switch(message->type) {
- case MessageType::TEXT:
- related_to_body = message->body;
+ case MessageType::TEXT: {
+ if(!message->replaces_event_id.empty() && strncmp(message->body.c_str(), " * ", 3) == 0)
+ related_to_body = message->body.substr(3);
+ else
+ related_to_body = message->body;
break;
+ }
case MessageType::IMAGE:
related_to_body = "sent an image";
break;
@@ -795,7 +794,13 @@ namespace QuickMedia {
return PluginResult::ERR;
}
- Message *relates_to_message = (Message*)relates_to;
+ Message *relates_to_message_raw = (Message*)relates_to;
+ std::shared_ptr<Message> relates_to_message_shared = room_it->second->message_by_event_id[relates_to_message_raw->event_id];
+ std::shared_ptr<Message> relates_to_message_original = get_edited_message_original_message(room_it->second.get(), relates_to_message_shared);
+ if(!relates_to_message_original) {
+ fprintf(stderr, "Failed to get the original message for message with event id: %s\n", relates_to_message_raw->event_id.c_str());
+ return PluginResult::ERR;
+ }
char random_characters[18];
if(!generate_random_characters(random_characters, sizeof(random_characters)))
@@ -804,14 +809,14 @@ namespace QuickMedia {
std::string random_readable_chars = random_characters_to_readable_string(random_characters, sizeof(random_characters));
Json::Value in_reply_to_json(Json::objectValue);
- in_reply_to_json["event_id"] = relates_to_message->event_id;
+ in_reply_to_json["event_id"] = relates_to_message_original->event_id;
Json::Value relates_to_json(Json::objectValue);
relates_to_json["m.in_reply_to"] = std::move(in_reply_to_json);
Json::Value request_data(Json::objectValue);
request_data["msgtype"] = message_type_to_request_msg_type_str(MessageType::TEXT);
- request_data["body"] = create_body_for_message_reply(room_it->second.get(), relates_to_message, body);
+ request_data["body"] = create_body_for_message_reply(room_it->second.get(), relates_to_message_raw, body); // Yes, the reply is to the edited message but the event_id reference is to the original message...
request_data["m.relates_to"] = std::move(relates_to_json);
Json::StreamWriterBuilder builder;
@@ -856,6 +861,204 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ PluginResult Matrix::post_edit(const std::string &room_id, const std::string &body, void *relates_to) {
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end()) {
+ fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
+ return PluginResult::ERR;
+ }
+
+ Message *relates_to_message_raw = (Message*)relates_to;
+ std::shared_ptr<Message> relates_to_message_shared = room_it->second->message_by_event_id[relates_to_message_raw->event_id];
+ std::shared_ptr<Message> relates_to_message_original = get_edited_message_original_message(room_it->second.get(), relates_to_message_shared);
+ if(!relates_to_message_original) {
+ fprintf(stderr, "Failed to get the original message for message with event id: %s\n", relates_to_message_raw->event_id.c_str());
+ return PluginResult::ERR;
+ }
+
+ char random_characters[18];
+ if(!generate_random_characters(random_characters, sizeof(random_characters)))
+ return PluginResult::ERR;
+
+ std::string random_readable_chars = random_characters_to_readable_string(random_characters, sizeof(random_characters));
+
+ std::string formatted_body;
+ bool contains_formatted_text = false;
+ int line = 0;
+ string_split(body, '\n', [&formatted_body, &contains_formatted_text, &line](const char *str, size_t size){
+ if(line > 0)
+ formatted_body += "<br/>";
+ if(size > 0 && str[0] == '>') {
+ std::string line(str, size);
+ html_escape_sequences(line);
+ formatted_body += "<font color=\"#789922\">";
+ formatted_body += line;
+ formatted_body += "</font>";
+ contains_formatted_text = true;
+ } else {
+ formatted_body.append(str, size);
+ }
+ ++line;
+ return true;
+ });
+
+ Json::Value new_content_json(Json::objectValue);
+ new_content_json["msgtype"] = "m.text";
+ new_content_json["body"] = body;
+ if(contains_formatted_text) {
+ new_content_json["format"] = "org.matrix.custom.html";
+ new_content_json["formatted_body"] = formatted_body;
+ }
+
+ Json::Value relates_to_json(Json::objectValue);
+ relates_to_json["event_id"] = relates_to_message_original->event_id;
+ relates_to_json["rel_type"] = "m.replace";
+
+ Json::Value request_data(Json::objectValue);
+ request_data["msgtype"] = message_type_to_request_msg_type_str(MessageType::TEXT);
+ request_data["body"] = " * " + body;
+ if(contains_formatted_text) {
+ request_data["format"] = "org.matrix.custom.html";
+ request_data["formatted_body"] = " * " + formatted_body;
+ }
+ request_data["m.new_content"] = std::move(new_content_json);
+ request_data["m.relates_to"] = std::move(relates_to_json);
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "PUT" },
+ { "-H", "content-type: application/json" },
+ { "-H", "Authorization: Bearer " + access_token },
+ { "--data-binary", Json::writeString(builder, std::move(request_data)) }
+ };
+
+ char request_url[512];
+ snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str());
+ fprintf(stderr, "Post message to |%s|\n", request_url);
+
+ std::string server_response;
+ if(download_to_string(request_url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
+
+ if(server_response.empty())
+ return PluginResult::ERR;
+
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &json_root, &json_errors)) {
+ fprintf(stderr, "Matrix post message response parse error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &event_id_json = json_root["event_id"];
+ if(!event_id_json.isString())
+ return PluginResult::ERR;
+
+ fprintf(stderr, "Matrix post edit, response event id: %s\n", event_id_json.asCString());
+ return PluginResult::OK;
+ }
+
+ // 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->replaces_event_id.empty())
+ return message;
+
+ auto message_it = room_data->message_by_event_id.find(message->replaces_event_id);
+ if(message_it == room_data->message_by_event_id.end()) {
+ Json::Value request_data(Json::objectValue);
+ request_data["lazy_load_members"] = true;
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string filter = url_param_encode(Json::writeString(builder, std::move(request_data)));
+
+ 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());
+ fprintf(stderr, "get message context, url: |%s|\n", url);
+
+ std::string server_response;
+ if(download_to_string(url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
+ return nullptr;
+
+ if(server_response.empty())
+ return nullptr;
+
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &json_root, &json_errors)) {
+ fprintf(stderr, "Matrix /rooms/<room_id>/context/<event_id> response parse error: %s\n", json_errors.c_str());
+ return nullptr;
+ }
+
+ if(!json_root.isObject())
+ return nullptr;
+
+ const Json::Value &event_json = json_root["event"];
+ if(!event_json.isObject())
+ return nullptr;
+
+ const Json::Value &event_id_json = event_json["event_id"];
+ if(!event_id_json.isString())
+ return nullptr;
+
+ const Json::Value &content_json = event_json["content"];
+ if(!content_json.isObject())
+ return nullptr;
+
+ const Json::Value &body_json = content_json["body"];
+ if(!body_json.isString())
+ return nullptr;
+
+ std::string replaces_event_id;
+ const Json::Value &relates_to_json = content_json["m.relates_to"];
+ if(relates_to_json.isObject()) {
+ const Json::Value &event_id_json = relates_to_json["event_id"];
+ const Json::Value &rel_type_json = relates_to_json["rel_type"];
+ if(event_id_json.isString() && rel_type_json.isString() && strcmp(rel_type_json.asCString(), "m.replace") == 0)
+ replaces_event_id = event_id_json.asString();
+ }
+
+ const Json::Value &content_type = content_json["msgtype"];
+ if(!content_type.isString())
+ return nullptr;
+
+ auto new_message = std::make_shared<Message>();
+ new_message->user_id = -1;
+ new_message->event_id = event_id_json.asString();
+ new_message->replaces_event_id = std::move(replaces_event_id);
+ if(strcmp(content_type.asCString(), "m.text") == 0) {
+ new_message->type = MessageType::TEXT;
+ } else if(strcmp(content_type.asCString(), "m.image") == 0) {
+ new_message->type = MessageType::IMAGE;
+ } else if(strcmp(content_type.asCString(), "m.video") == 0) {
+ new_message->type = MessageType::VIDEO;
+ } else {
+ return nullptr;
+ }
+
+ return get_edited_message_original_message(room_data, std::move(new_message));
+ } else {
+ return get_edited_message_original_message(room_data, message_it->second);
+ }
+ }
+
// Returns empty string on error
static const char* file_get_filename(const std::string &filepath) {
size_t index = filepath.rfind('/');
@@ -1155,4 +1358,14 @@ namespace QuickMedia {
return PluginResult::OK;
}
+
+ bool Matrix::was_message_posted_by_me(const std::string &room_id, void *message) const {
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end()) {
+ fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
+ return false;
+ }
+ Message *message_typed = (Message*)message;
+ return user_id == room_it->second->user_info[message_typed->user_id].user_id;
+ }
} \ No newline at end of file