diff --git a/include/mtx/events/common.hpp b/include/mtx/events/common.hpp index 9da0c615a..b31c66393 100644 --- a/include/mtx/events/common.hpp +++ b/include/mtx/events/common.hpp @@ -143,31 +143,18 @@ from_json(const nlohmann::json &obj, VideoInfo &info); void to_json(nlohmann::json &obj, const VideoInfo &info); -//! In reply to data for rich replies (notice and text events) -struct InReplyTo -{ - //! Event id being replied to - std::string event_id; -}; - -//! Deserialization method needed by @p nlohmann::json. -void -from_json(const nlohmann::json &obj, InReplyTo &in_reply_to); - -//! Serialization method needed by @p nlohmann::json. -void -to_json(nlohmann::json &obj, const InReplyTo &in_reply_to); - //! Definition of rel_type for relations. enum class RelationType { - // m.annotation rel_type + //! m.annotation rel_type Annotation, - // m.reference rel_type + //! m.reference rel_type Reference, - // m.replace rel_type + //! m.replace rel_type Replace, - // not one of the supported types + //! im.nheko.relations.v1.in_reply_to rel_type + InReplyTo, + //! not one of the supported types Unsupported }; @@ -178,38 +165,51 @@ void to_json(nlohmann::json &obj, const RelationType &type); //! Relates to for reactions -struct RelatesTo +struct Relation { - // Type of relation - RelationType rel_type; - // event id being reacted to - std::string event_id; - // key is the reaction itself - std::optional key; + //! Type of relation + RelationType rel_type = RelationType::Unsupported; + //! event id being reacted to + std::string event_id = ""; + //! key is the reaction itself + std::optional key = std::nullopt; }; - -//! Deserialization method needed by @p nlohmann::json. void -from_json(const nlohmann::json &obj, RelatesTo &relates_to); - -//! Serialization method needed by @p nlohmann::json. +from_json(const nlohmann::json &obj, Relation &relation); void -to_json(nlohmann::json &obj, const RelatesTo &relates_to); +to_json(nlohmann::json &obj, const Relation &relation); -//! Relates to data for rich replies (notice and text events) -struct ReplyRelatesTo +//! Multiple relations for a event +struct Relations { - //! What the message is in reply to - InReplyTo in_reply_to; + //! All the relations for this event + std::vector relations; + //! Flag, if we generated this from relates_to relations or used + //! im.nheko.relactions.v1.relations + bool synthesized = false; + + std::optional reply_to() const; + std::optional replaces() const; + std::optional references() const; + std::optional annotates() const; }; -//! Deserialization method needed by @p nlohmann::json. -void -from_json(const nlohmann::json &obj, ReplyRelatesTo &relates_to); +/// @brief Parses relations from a content object +/// +/// @param obj The content object of an event. +Relations +parse_relations(const nlohmann::json &obj); -//! Serialization method needed by @p nlohmann::json. +/// @brief Serializes relations to a content object +/// +/// @param obj The content object of an event. void -to_json(nlohmann::json &obj, const ReplyRelatesTo &relates_to); +add_relations(nlohmann::json &obj, const Relations &relations); +/// @brief Applies also all the edit rules to the event in addition to adding the relations +/// +/// @param obj The content object of an event. +void +apply_relations(nlohmann::json &obj, const Relations &relations); } // namespace common } // namespace mtx diff --git a/include/mtx/events/encrypted.hpp b/include/mtx/events/encrypted.hpp index 246416908..7eeef31b1 100644 --- a/include/mtx/events/encrypted.hpp +++ b/include/mtx/events/encrypted.hpp @@ -81,7 +81,6 @@ from_json(const nlohmann::json &obj, OlmEncrypted &event); void to_json(nlohmann::json &obj, const OlmEncrypted &event); -// !TODO Change the RelatesTo to handle ReplyRelatesTo type of event //! Content of the `m.room.encrypted` event. struct Encrypted { @@ -95,10 +94,8 @@ struct Encrypted std::string sender_key; //! Outbound group session id. std::string session_id; - //! Relates to for rich replies - common::ReplyRelatesTo relates_to; - //! Relates to used for verification messages - common::RelatesTo r_relates_to; + //! Relations like rich replies + common::Relations relations; }; void @@ -271,7 +268,7 @@ struct KeyVerificationStart /// /// @note Will be used only for room-verification msgs where this is used in place of /// transaction_id. - std::optional relates_to; + common::Relations relations; }; void @@ -292,7 +289,7 @@ struct KeyVerificationReady //! this is used for relating this message with previously sent //! key.verification.request will be used only for room-verification msgs where this //! is used in place of txnid - std::optional relates_to; + common::Relations relations; }; void @@ -308,7 +305,7 @@ struct KeyVerificationDone std::optional transaction_id; //! this is used for relating this message with previously sent key.verification.request //! will be used only for room-verification msgs where this is used in place of txnid - std::optional relates_to; + common::Relations relations; }; void @@ -344,7 +341,7 @@ struct KeyVerificationAccept std::string commitment; //! this is used for relating this message with previously sent key.verification.request //! will be used only for room-verification msgs where this is used in place of txnid - std::optional relates_to; + common::Relations relations; }; void @@ -389,7 +386,7 @@ struct KeyVerificationCancel std::string code; //! this is used for relating this message with previously sent key.verification.request //! will be used only for room-verification msgs where this is used in place of txnid - std::optional relates_to; + common::Relations relations; }; void @@ -407,7 +404,7 @@ struct KeyVerificationKey std::string key; //! this is used for relating this message with previously sent key.verification.request //! will be used only for room-verification msgs where this is used in place of txnid - std::optional relates_to; + common::Relations relations; }; void @@ -429,7 +426,7 @@ struct KeyVerificationMac std::string keys; //! this is used for relating this message with previously sent key.verification.request //! will be used only for room-verification msgs where this is used in place of txnid - std::optional relates_to; + common::Relations relations; }; void diff --git a/include/mtx/events/messages/audio.hpp b/include/mtx/events/messages/audio.hpp index 0c7207cfb..4e2df4c90 100644 --- a/include/mtx/events/messages/audio.hpp +++ b/include/mtx/events/messages/audio.hpp @@ -34,7 +34,7 @@ struct Audio //! Encryption members. If present, they replace url. std::optional file; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; void diff --git a/include/mtx/events/messages/emote.hpp b/include/mtx/events/messages/emote.hpp index 5ab5d2a5b..47e291c99 100644 --- a/include/mtx/events/messages/emote.hpp +++ b/include/mtx/events/messages/emote.hpp @@ -29,7 +29,7 @@ struct Emote //! HTML formatted message. std::string formatted_body; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; void diff --git a/include/mtx/events/messages/file.hpp b/include/mtx/events/messages/file.hpp index baa794fcc..f5b23df07 100644 --- a/include/mtx/events/messages/file.hpp +++ b/include/mtx/events/messages/file.hpp @@ -39,7 +39,7 @@ struct File //! Encryption members. If present, they replace url. std::optional file; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; void diff --git a/include/mtx/events/messages/image.hpp b/include/mtx/events/messages/image.hpp index 8307da28d..f64f33386 100644 --- a/include/mtx/events/messages/image.hpp +++ b/include/mtx/events/messages/image.hpp @@ -35,7 +35,7 @@ struct Image // Encryption members. If present, they replace url. std::optional file; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; //! Content of `m.sticker`. @@ -52,7 +52,7 @@ struct StickerImage // Encryption members. If present, they replace url. std::optional file; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; void diff --git a/include/mtx/events/messages/notice.hpp b/include/mtx/events/messages/notice.hpp index b222f85e2..c67024fac 100644 --- a/include/mtx/events/messages/notice.hpp +++ b/include/mtx/events/messages/notice.hpp @@ -29,7 +29,7 @@ struct Notice //! HTML formatted message. std::string formatted_body; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; void diff --git a/include/mtx/events/messages/text.hpp b/include/mtx/events/messages/text.hpp index a64a74977..3b60e0513 100644 --- a/include/mtx/events/messages/text.hpp +++ b/include/mtx/events/messages/text.hpp @@ -30,7 +30,7 @@ struct Text //! HTML formatted message. std::string formatted_body; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; void diff --git a/include/mtx/events/messages/video.hpp b/include/mtx/events/messages/video.hpp index 1b33bb8d3..80ec1f76f 100644 --- a/include/mtx/events/messages/video.hpp +++ b/include/mtx/events/messages/video.hpp @@ -34,7 +34,7 @@ struct Video //! Encryption members. If present, they replace url. std::optional file; //! Relates to for rich replies - mtx::common::ReplyRelatesTo relates_to; + mtx::common::Relations relations; }; void diff --git a/include/mtx/events/reaction.hpp b/include/mtx/events/reaction.hpp index e8f8106f8..320354a8a 100644 --- a/include/mtx/events/reaction.hpp +++ b/include/mtx/events/reaction.hpp @@ -20,8 +20,8 @@ namespace msg { //! Content for the `m.reaction` event. struct Reaction { - //! The event being reacted to - mtx::common::RelatesTo relates_to; + //! Should be an annotation relation + common::Relations relations; }; void diff --git a/include/mtx/events_impl.hpp b/include/mtx/events_impl.hpp index 9ba6bf061..6ff565d95 100644 --- a/include/mtx/events_impl.hpp +++ b/include/mtx/events_impl.hpp @@ -5,6 +5,18 @@ #include "mtx/events/unknown.hpp" namespace mtx::events { +namespace detail { + +template +struct can_edit : std::false_type +{}; + +template +struct can_edit> + : std::is_same +{}; +} + template [[gnu::used, llvm::used]] void to_json(json &obj, const Event &event) @@ -21,9 +33,20 @@ template [[gnu::used, llvm::used]] void from_json(const json &obj, Event &event) { - event.content = obj.at("content").get(); - event.type = getEventType(obj.at("type").get()); - event.sender = obj.value("sender", ""); + if (obj.at("content").contains("m.new_content")) { + auto new_content = obj.at("content"); + for (const auto &e : obj["content"]["m.new_content"].items()) { + if (e.key() != "m.relates_to" && + e.key() != "im.nheko.relations.v1.relations") + new_content[e.key()] = e.value(); + } + event.content = new_content.get(); + } else { + event.content = obj.at("content").get(); + } + + event.type = getEventType(obj.at("type").get()); + event.sender = obj.value("sender", ""); if constexpr (std::is_same_v) event.content.type = obj.at("type").get(); diff --git a/lib/structs/events/common.cpp b/lib/structs/events/common.cpp index 4551e1dbd..e8e080e04 100644 --- a/lib/structs/events/common.cpp +++ b/lib/structs/events/common.cpp @@ -169,19 +169,6 @@ to_json(json &obj, const VideoInfo &info) obj["xyz.amorgan.blurhash"] = info.blurhash; } -void -from_json(const json &obj, InReplyTo &in_reply_to) -{ - if (obj.find("event_id") != obj.end()) - in_reply_to.event_id = obj.at("event_id").get(); -} - -void -to_json(json &obj, const InReplyTo &in_reply_to) -{ - obj["event_id"] = in_reply_to.event_id; -} - void to_json(json &obj, const RelationType &type) { @@ -195,6 +182,9 @@ to_json(json &obj, const RelationType &type) case RelationType::Replace: obj = "m.replace"; break; + case RelationType::InReplyTo: + obj = "im.nheko.relations.v1.in_reply_to"; + break; case RelationType::Unsupported: default: obj = "unsupported"; @@ -211,12 +201,128 @@ from_json(const json &obj, RelationType &type) type = RelationType::Reference; else if (obj.get() == "m.replace") type = RelationType::Replace; + else if (obj.get() == "im.nheko.relations.v1.in_reply_to") + type = RelationType::InReplyTo; else type = RelationType::Unsupported; } +Relations +parse_relations(const nlohmann::json &content) +{ + try { + if (content.contains("im.nheko.relations.v1.relations")) { + Relations rels; + rels.relations = content.at("im.nheko.relations.v1.relations") + .get>(); + rels.synthesized = false; + return rels; + } else if (content.contains("m.relates_to")) { + if (content.at("m.relates_to").contains("m.in_reply_to")) { + Relation r; + r.event_id = content.at("m.relates_to") + .at("m.in_reply_to") + .at("event_id") + .get(); + r.rel_type = RelationType::InReplyTo; + + Relations rels; + rels.relations.push_back(r); + rels.synthesized = true; + return rels; + } else { + Relation r = + content.at("m.relates_to").get(); + Relations rels; + rels.relations.push_back(r); + rels.synthesized = true; + + if (r.rel_type == RelationType::Replace && + content.contains("m.new_content") && + content.at("m.new_content").contains("m.relates_to")) { + const auto secondRel = + content["m.new_content"]["m.relates_to"]; + if (secondRel.contains("m.in_reply_to")) { + Relation r2{}; + r.rel_type = RelationType::InReplyTo; + r.event_id = secondRel.at("m.in_reply_to") + .at("event_id") + .get(); + rels.relations.push_back(r2); + } else { + rels.relations.push_back(secondRel.get()); + } + } + + return rels; + } + } + } catch (nlohmann::json &e) { + } + return {}; +} + void -from_json(const json &obj, RelatesTo &relates_to) +add_relations(nlohmann::json &content, const Relations &relations) +{ + if (relations.relations.empty()) + return; + + std::optional edit, not_edit; + for (const auto &r : relations.relations) { + if (r.rel_type == RelationType::Replace) + edit = r; + else + not_edit = r; + } + + if (not_edit) { + if (not_edit->rel_type == RelationType::InReplyTo) { + content["m.relates_to"]["m.in_reply_to"]["event_id"] = not_edit->event_id; + } else { + content["m.relates_to"] = *not_edit; + } + } + + if (edit) { + if (not_edit) + content["m.new_content"]["m.relates_to"] = content["m.relates_to"]; + content["m.relates_to"] = *edit; + } + + if (!relations.synthesized) { + for (const auto &r : relations.relations) { + if (r.rel_type != RelationType::Unsupported) + content["im.nheko.relations.v1.relations"].push_back(r); + } + } +} +void +apply_relations(nlohmann::json &content, const Relations &relations) +{ + add_relations(content, relations); + + if (relations.replaces()) { + for (const auto &e : content.items()) { + if (e.key() != "m.relates_to" && + e.key() != "im.nheko.relations.v1.relations" && + e.key() != "m.new_content") { + content["m.new_content"][e.key()] = e.value(); + } + } + + if (content.contains("body")) { + content["body"] = "* " + content["body"].get(); + } + if (content.contains("formatted_body")) { + content["formatted_body"] = + "* " + content["formatted_body"].get(); + } + } +} + +void +from_json(const json &obj, Relation &relates_to) { if (obj.find("rel_type") != obj.end()) relates_to.rel_type = obj.at("rel_type").get(); @@ -227,7 +333,7 @@ from_json(const json &obj, RelatesTo &relates_to) } void -to_json(json &obj, const RelatesTo &relates_to) +to_json(json &obj, const Relation &relates_to) { obj["rel_type"] = relates_to.rel_type; obj["event_id"] = relates_to.event_id; @@ -235,18 +341,36 @@ to_json(json &obj, const RelatesTo &relates_to) obj["key"] = relates_to.key.value(); } -void -from_json(const json &obj, ReplyRelatesTo &relates_to) +static inline std::optional +return_first_relation_matching(RelationType t, const Relations &rels) { - if (obj.find("m.in_reply_to") != obj.end()) - relates_to.in_reply_to = obj.at("m.in_reply_to").get(); + for (const auto &r : rels.relations) + if (r.rel_type == t) + return r.event_id; + return std::nullopt; } - -void -to_json(json &obj, const ReplyRelatesTo &relates_to) +std::optional +Relations::reply_to() const { - obj["m.in_reply_to"] = relates_to.in_reply_to; + return return_first_relation_matching(RelationType::InReplyTo, *this); +} +std::optional +Relations::replaces() const +{ + return return_first_relation_matching(RelationType::Replace, *this); +} +std::optional +Relations::references() const +{ + return return_first_relation_matching(RelationType::Reference, *this); +} +std::optional +Relations::annotates() const +{ + for (const auto &r : relations) + if (r.rel_type == RelationType::Annotation) + return r; + return std::nullopt; } - } // namespace common } // namespace mtx diff --git a/lib/structs/events/encrypted.cpp b/lib/structs/events/encrypted.cpp index 432f365df..04d80dee7 100644 --- a/lib/structs/events/encrypted.cpp +++ b/lib/structs/events/encrypted.cpp @@ -100,12 +100,7 @@ from_json(const json &obj, Encrypted &content) content.device_id = obj.at("device_id").get(); content.sender_key = obj.at("sender_key").get(); content.session_id = obj.at("session_id").get(); - if (obj.count("m.relates_to") != 0) { - if (obj.at("m.relates_to").contains("m.in_reply_to")) - content.relates_to = obj.at("m.relates_to").get(); - else - content.r_relates_to = obj.at("m.relates_to").get(); - } + content.relations = common::parse_relations(obj); } void @@ -117,10 +112,8 @@ to_json(json &obj, const Encrypted &content) obj["sender_key"] = content.sender_key; obj["session_id"] = content.session_id; - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; - if (!content.r_relates_to.event_id.empty()) - obj["m.relates_to"] = content.r_relates_to; + // For encrypted events, only add releations, don't generate new_content and friends + common::add_relations(obj, content.relations); } void @@ -272,8 +265,7 @@ from_json(const json &obj, KeyVerificationStart &event) obj.at("message_authentication_codes").get>(); event.short_authentication_string = obj.at("short_authentication_string").get>(); - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + event.relations = common::parse_relations(obj); } void @@ -289,8 +281,7 @@ to_json(json &obj, const KeyVerificationStart &event) obj["hashes"] = event.hashes; obj["message_authentication_codes"] = event.message_authentication_codes; obj["short_authentication_string"] = event.short_authentication_string; - if (event.relates_to.has_value()) - obj["m.relates_to"] = event.relates_to.value(); + common::apply_relations(obj, event.relations); } void @@ -301,8 +292,7 @@ from_json(const json &obj, KeyVerificationReady &event) } event.methods = obj.at("methods").get>(); event.from_device = obj.at("from_device").get(); - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + event.relations = common::parse_relations(obj); } void @@ -312,8 +302,7 @@ to_json(json &obj, const KeyVerificationReady &event) if (event.transaction_id.has_value()) obj["transaction_id"] = event.transaction_id.value(); obj["from_device"] = event.from_device; - if (event.relates_to.has_value()) - obj["m.relates_to"] = event.relates_to.value(); + common::apply_relations(obj, event.relations); } void @@ -322,8 +311,7 @@ from_json(const nlohmann::json &obj, KeyVerificationDone &event) if (obj.count("transaction_id") != 0) { event.transaction_id = obj.at("transaction_id").get(); } - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + event.relations = common::parse_relations(obj); } void @@ -331,8 +319,7 @@ to_json(nlohmann::json &obj, const KeyVerificationDone &event) { if (event.transaction_id.has_value()) obj["transaction_id"] = event.transaction_id.value(); - if (event.relates_to.has_value()) - obj["m.relates_to"] = event.relates_to.value(); + common::apply_relations(obj, event.relations); } void @@ -349,8 +336,7 @@ from_json(const json &obj, KeyVerificationAccept &event) obj.at("short_authentication_string").get>(); event.commitment = obj.at("commitment").get(); event.method = obj.value("method", VerificationMethods::SASv1); - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + event.relations = common::parse_relations(obj); } void @@ -364,8 +350,7 @@ to_json(json &obj, const KeyVerificationAccept &event) obj["short_authentication_string"] = event.short_authentication_string; obj["commitment"] = event.commitment; obj["method"] = event.method; - if (event.relates_to.has_value()) - obj["m.relates_to"] = event.relates_to.value(); + common::apply_relations(obj, event.relations); } void @@ -374,10 +359,9 @@ from_json(const json &obj, KeyVerificationCancel &event) if (obj.count("transaction_id") != 0) { event.transaction_id = obj.at("transaction_id").get(); } - event.reason = obj.value("reason", ""); - event.code = obj.value("code", ""); - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + event.reason = obj.value("reason", ""); + event.code = obj.value("code", ""); + event.relations = common::parse_relations(obj); } void @@ -387,8 +371,7 @@ to_json(json &obj, const KeyVerificationCancel &event) obj["transaction_id"] = event.transaction_id.value(); obj["reason"] = event.reason; obj["code"] = event.code; - if (event.relates_to.has_value()) - obj["m.relates_to"] = event.relates_to.value(); + common::apply_relations(obj, event.relations); } void @@ -397,9 +380,8 @@ from_json(const json &obj, KeyVerificationKey &event) if (obj.count("transaction_id") != 0) { event.transaction_id = obj.at("transaction_id").get(); } - event.key = obj.at("key").get(); - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + event.key = obj.at("key").get(); + event.relations = common::parse_relations(obj); } void @@ -408,8 +390,7 @@ to_json(json &obj, const KeyVerificationKey &event) if (event.transaction_id.has_value()) obj["transaction_id"] = event.transaction_id.value(); obj["key"] = event.key; - if (event.relates_to.has_value()) - obj["m.relates_to"] = event.relates_to.value(); + common::apply_relations(obj, event.relations); } void @@ -418,10 +399,9 @@ from_json(const json &obj, KeyVerificationMac &event) if (obj.count("transaction_id") != 0) { event.transaction_id = obj.at("transaction_id").get(); } - event.mac = obj.at("mac").get>(); - event.keys = obj.at("keys").get(); - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + event.mac = obj.at("mac").get>(); + event.keys = obj.at("keys").get(); + event.relations = common::parse_relations(obj); } void @@ -431,8 +411,7 @@ to_json(json &obj, const KeyVerificationMac &event) obj["transaction_id"] = event.transaction_id.value(); obj["mac"] = event.mac; obj["keys"] = event.keys; - if (event.relates_to.has_value()) - obj["m.relates_to"] = event.relates_to.value(); + common::apply_relations(obj, event.relations); } void diff --git a/lib/structs/events/messages/audio.cpp b/lib/structs/events/messages/audio.cpp index 87fbe3760..c44a85b84 100644 --- a/lib/structs/events/messages/audio.cpp +++ b/lib/structs/events/messages/audio.cpp @@ -26,8 +26,7 @@ from_json(const json &obj, Audio &content) if (obj.find("file") != obj.end()) content.file = obj.at("file").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void @@ -42,8 +41,7 @@ to_json(json &obj, const Audio &content) else obj["url"] = content.url; - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/lib/structs/events/messages/emote.cpp b/lib/structs/events/messages/emote.cpp index 7a15fd5c5..abe97d751 100644 --- a/lib/structs/events/messages/emote.cpp +++ b/lib/structs/events/messages/emote.cpp @@ -22,8 +22,7 @@ from_json(const json &obj, Emote &content) if (obj.count("formatted_body") != 0) content.formatted_body = obj.at("formatted_body").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void @@ -37,8 +36,7 @@ to_json(json &obj, const Emote &content) obj["formatted_body"] = content.formatted_body; } - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/lib/structs/events/messages/file.cpp b/lib/structs/events/messages/file.cpp index abc80e343..ead6edbaf 100644 --- a/lib/structs/events/messages/file.cpp +++ b/lib/structs/events/messages/file.cpp @@ -29,25 +29,25 @@ from_json(const json &obj, File &content) if (obj.find("file") != obj.end()) content.file = obj.at("file").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void to_json(json &obj, const File &content) { - obj["msgtype"] = "m.file"; - obj["body"] = content.body; - obj["filename"] = content.filename; - obj["info"] = content.info; + obj["msgtype"] = "m.file"; + obj["body"] = content.body; + + if (!content.filename.empty()) + obj["filename"] = content.filename; + obj["info"] = content.info; if (content.file) obj["file"] = content.file.value(); else obj["url"] = content.url; - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/lib/structs/events/messages/image.cpp b/lib/structs/events/messages/image.cpp index 1d9fe2c28..4e4acf7ed 100644 --- a/lib/structs/events/messages/image.cpp +++ b/lib/structs/events/messages/image.cpp @@ -25,8 +25,7 @@ from_json(const json &obj, Image &content) if (obj.find("file") != obj.end()) content.file = obj.at("file").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void @@ -41,8 +40,7 @@ to_json(json &obj, const Image &content) else obj["url"] = content.url; - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } void @@ -58,8 +56,7 @@ from_json(const json &obj, StickerImage &content) if (obj.find("file") != obj.end()) content.file = obj.at("file").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void @@ -73,8 +70,7 @@ to_json(json &obj, const StickerImage &content) else obj["url"] = content.url; - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/lib/structs/events/messages/notice.cpp b/lib/structs/events/messages/notice.cpp index cd59cf386..258bf8edb 100644 --- a/lib/structs/events/messages/notice.cpp +++ b/lib/structs/events/messages/notice.cpp @@ -22,8 +22,7 @@ from_json(const json &obj, Notice &content) if (obj.count("formatted_body") != 0) content.formatted_body = obj.at("formatted_body").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void @@ -37,7 +36,7 @@ to_json(json &obj, const Notice &content) obj["formatted_body"] = content.formatted_body; } - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/lib/structs/events/messages/text.cpp b/lib/structs/events/messages/text.cpp index 5c0bf7a75..dd80187d9 100644 --- a/lib/structs/events/messages/text.cpp +++ b/lib/structs/events/messages/text.cpp @@ -22,8 +22,7 @@ from_json(const json &obj, Text &content) if (obj.count("formatted_body") != 0) content.formatted_body = obj.at("formatted_body").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void @@ -37,8 +36,7 @@ to_json(json &obj, const Text &content) obj["formatted_body"] = content.formatted_body; } - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/lib/structs/events/messages/video.cpp b/lib/structs/events/messages/video.cpp index 884184a60..3f8fb52f1 100644 --- a/lib/structs/events/messages/video.cpp +++ b/lib/structs/events/messages/video.cpp @@ -27,8 +27,7 @@ from_json(const json &obj, Video &content) if (obj.find("file") != obj.end()) content.file = obj.at("file").get(); - if (obj.count("m.relates_to") != 0) - content.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void @@ -43,8 +42,7 @@ to_json(json &obj, const Video &content) else obj["url"] = content.url; - if (!content.relates_to.in_reply_to.event_id.empty()) - obj["m.relates_to"] = content.relates_to; + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/lib/structs/events/reaction.cpp b/lib/structs/events/reaction.cpp index c897478db..43c68342f 100644 --- a/lib/structs/events/reaction.cpp +++ b/lib/structs/events/reaction.cpp @@ -10,16 +10,17 @@ namespace events { namespace msg { void -from_json(const json &obj, Reaction &event) +from_json(const json &obj, Reaction &content) { - if (obj.count("m.relates_to") != 0) - event.relates_to = obj.at("m.relates_to").get(); + content.relations = common::parse_relations(obj); } void -to_json(json &obj, const Reaction &event) +to_json(json &obj, const Reaction &content) { - obj["m.relates_to"] = event.relates_to; + obj = nlohmann::json::object(); + + common::apply_relations(obj, content.relations); } } // namespace msg diff --git a/tests/messages.cpp b/tests/messages.cpp index c98ab6a79..649f83339 100644 --- a/tests/messages.cpp +++ b/tests/messages.cpp @@ -35,10 +35,11 @@ TEST(RoomEvents, Reaction) EXPECT_EQ(event.sender, "@example:localhost"); EXPECT_EQ(event.origin_server_ts, 1588536414112L); EXPECT_EQ(event.unsigned_data.age, 1905609L); - EXPECT_EQ(event.content.relates_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$oGKg0tfsnDamWPsGxUptGLWR5b8Xq6QNFFsysQNSnake"); - EXPECT_EQ(event.content.relates_to.key, "👀"); - EXPECT_EQ(event.content.relates_to.rel_type, mtx::common::RelationType::Annotation); + EXPECT_EQ(event.content.relations.relations.at(0).key, "👀"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::Annotation); EXPECT_EQ(data.dump(), json(event).dump()); } @@ -112,8 +113,10 @@ TEST(RoomEvents, AudioMessage) EXPECT_EQ(event.content.info.mimetype, "audio/mpeg"); EXPECT_EQ(event.content.info.size, 1563685); EXPECT_EQ(event.content.info.duration, 2140786); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.reply_to().value(), "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); } TEST(RoomEvents, EmoteMessage) @@ -148,8 +151,10 @@ TEST(RoomEvents, EmoteMessage) EXPECT_EQ(event.unsigned_data.age, 626351821); EXPECT_EQ(event.content.body, "tests"); EXPECT_EQ(event.content.msgtype, "m.emote"); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); } TEST(RoomEvents, FileMessage) @@ -202,8 +207,10 @@ TEST(RoomEvents, FileMessage) EXPECT_EQ(event.content.url, "mxc://matrix.org/XpxykZBESCSQnYkLKbbIKnVn"); EXPECT_EQ(event.content.info.mimetype, "application/pdf"); EXPECT_EQ(event.content.info.size, 40565); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); json withThumb = event; EXPECT_EQ(withThumb["content"]["info"].count("thumbnail_url"), 1); @@ -350,8 +357,10 @@ TEST(RoomEvents, ImageMessage) EXPECT_EQ(event.content.info.thumbnail_info.w, 474); EXPECT_EQ(event.content.info.thumbnail_info.h, 302); EXPECT_EQ(event.content.info.thumbnail_info.size, 33504); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); json withThumb = event; EXPECT_EQ(withThumb["content"]["info"].count("thumbnail_url"), 1); @@ -415,8 +424,10 @@ TEST(RoomEvents, ImageMessage) EXPECT_EQ(event.content.info.thumbnail_info.w, 0); EXPECT_EQ(event.content.info.thumbnail_info.h, 0); EXPECT_EQ(event.content.info.thumbnail_info.size, 0); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); } TEST(RoomEvents, LocationMessage) {} @@ -454,8 +465,10 @@ TEST(RoomEvents, NoticeMessage) EXPECT_EQ(event.content.body, "https://github.com/postmarketOS/pmbootstrap/issues/900 : Package nheko"); EXPECT_EQ(event.content.msgtype, "m.notice"); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); } TEST(RoomEvents, TextMessage) @@ -493,8 +506,10 @@ TEST(RoomEvents, TextMessage) EXPECT_EQ(event.content.body, "hey there"); EXPECT_EQ(event.content.msgtype, "m.text"); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); EXPECT_EQ(data.dump(), json(event).dump()); } @@ -556,8 +571,10 @@ TEST(RoomEvents, VideoMessage) EXPECT_EQ(event.content.info.thumbnail_info.h, 300); EXPECT_EQ(event.content.info.thumbnail_info.w, 310); EXPECT_EQ(event.content.info.thumbnail_info.size, 46144); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); } TEST(RoomEvents, Sticker) @@ -602,8 +619,10 @@ TEST(RoomEvents, Sticker) EXPECT_EQ(event.content.info.w, 140); EXPECT_EQ(event.content.info.h, 200); EXPECT_EQ(event.content.info.size, 73602); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); json data2 = R"({ "type": "m.sticker", @@ -742,8 +761,10 @@ TEST(RoomEvents, Encrypted) EXPECT_EQ(event.content.device_id, "RJYKSTBOIE"); EXPECT_EQ(event.content.sender_key, "IlRMeOPX2e0MurIyfWEucYBRVOEEUMrOHqn/8mLqMjA"); EXPECT_EQ(event.content.session_id, "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"); - EXPECT_EQ(event.content.relates_to.in_reply_to.event_id, + EXPECT_EQ(event.content.relations.relations.at(0).event_id, "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E"); + EXPECT_EQ(event.content.relations.relations.at(0).rel_type, + mtx::common::RelationType::InReplyTo); EXPECT_EQ(data, json(event)); }