summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--io.github.NhekoReborn.Nheko.json3
-rw-r--r--src/Cache.cpp72
-rw-r--r--src/DeviceVerificationFlow.cpp34
-rw-r--r--src/DeviceVerificationFlow.h10
-rw-r--r--src/EventAccessors.cpp35
-rw-r--r--src/EventAccessors.h6
-rw-r--r--src/Olm.cpp27
-rw-r--r--src/timeline/EventStore.cpp37
-rw-r--r--src/timeline/InputBar.cpp18
-rw-r--r--src/timeline/TimelineModel.cpp4
-rw-r--r--src/timeline/TimelineViewManager.cpp8
12 files changed, 112 insertions, 144 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d5245ef8..cf2b5959 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -356,7 +356,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        v0.4.1
+		GIT_TAG        70fa15de3ec84cf0c0ab6250f2e5e62f34a6d05b
 		)
 	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
 	set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json
index e6eeb123..98ab9629 100644
--- a/io.github.NhekoReborn.Nheko.json
+++ b/io.github.NhekoReborn.Nheko.json
@@ -220,8 +220,7 @@
       "name": "mtxclient",
       "sources": [
         {
-          "commit": "4951190c938740defa0988d98d5e861038622936",
-          "tag": "v0.4.1",
+          "commit": "70fa15de3ec84cf0c0ab6250f2e5e62f34a6d05b",
           "type": "git",
           "url": "https://github.com/Nheko-Reborn/mtxclient.git"
         }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 3f2bf73a..94b9a6a6 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2713,23 +2713,19 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
                         lmdb::dbi_put(txn, evToOrderDb, event_id, txn_order);
                         lmdb::dbi_del(txn, evToOrderDb, lmdb::val(txn_id));
 
-                        if (event.contains("content") &&
-                            event["content"].contains("m.relates_to")) {
-                                auto temp         = event["content"]["m.relates_to"];
-                                json relates_to_j = temp.contains("m.in_reply_to") &&
-                                                        temp["m.in_reply_to"].is_object()
-                                                      ? temp["m.in_reply_to"]["event_id"]
-                                                      : temp["event_id"];
-                                std::string relates_to =
-                                  relates_to_j.is_string() ? relates_to_j.get<std::string>() : "";
-
-                                if (!relates_to.empty()) {
-                                        lmdb::dbi_del(txn,
-                                                      relationsDb,
-                                                      lmdb::val(relates_to),
-                                                      lmdb::val(txn_id));
-                                        lmdb::dbi_put(
-                                          txn, relationsDb, lmdb::val(relates_to), event_id);
+                        auto relations = mtx::accessors::relations(e);
+                        if (!relations.relations.empty()) {
+                                for (const auto &r : relations.relations) {
+                                        if (!r.event_id.empty()) {
+                                                lmdb::dbi_del(txn,
+                                                              relationsDb,
+                                                              lmdb::val(r.event_id),
+                                                              lmdb::val(txn_id));
+                                                lmdb::dbi_put(txn,
+                                                              relationsDb,
+                                                              lmdb::val(r.event_id),
+                                                              event_id);
+                                        }
                                 }
                         }
 
@@ -2808,19 +2804,16 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
                                               lmdb::val(&msgIndex, sizeof(msgIndex)));
                         }
 
-                        if (event.contains("content") &&
-                            event["content"].contains("m.relates_to")) {
-                                auto temp         = event["content"]["m.relates_to"];
-                                json relates_to_j = temp.contains("m.in_reply_to") &&
-                                                        temp["m.in_reply_to"].is_object()
-                                                      ? temp["m.in_reply_to"]["event_id"]
-                                                      : temp["event_id"];
-                                std::string relates_to =
-                                  relates_to_j.is_string() ? relates_to_j.get<std::string>() : "";
-
-                                if (!relates_to.empty())
-                                        lmdb::dbi_put(
-                                          txn, relationsDb, lmdb::val(relates_to), event_id);
+                        auto relations = mtx::accessors::relations(e);
+                        if (!relations.relations.empty()) {
+                                for (const auto &r : relations.relations) {
+                                        if (!r.event_id.empty()) {
+                                                lmdb::dbi_put(txn,
+                                                              relationsDb,
+                                                              lmdb::val(r.event_id),
+                                                              event_id);
+                                        }
+                                }
                         }
                 }
         }
@@ -2901,17 +2894,14 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message
                           txn, msg2orderDb, event_id, lmdb::val(&msgIndex, sizeof(msgIndex)));
                 }
 
-                if (event.contains("content") && event["content"].contains("m.relates_to")) {
-                        auto temp = event["content"]["m.relates_to"];
-                        json relates_to_j =
-                          temp.contains("m.in_reply_to") && temp["m.in_reply_to"].is_object()
-                            ? temp["m.in_reply_to"]["event_id"]
-                            : temp["event_id"];
-                        std::string relates_to =
-                          relates_to_j.is_string() ? relates_to_j.get<std::string>() : "";
-
-                        if (!relates_to.empty())
-                                lmdb::dbi_put(txn, relationsDb, lmdb::val(relates_to), event_id);
+                auto relations = mtx::accessors::relations(e);
+                if (!relations.relations.empty()) {
+                        for (const auto &r : relations.relations) {
+                                if (!r.event_id.empty()) {
+                                        lmdb::dbi_put(
+                                          txn, relationsDb, lmdb::val(r.event_id), event_id);
+                                }
+                        }
                 }
         }
 
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 51ef79fd..c6277a9d 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -105,8 +105,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                         if (msg.transaction_id.has_value()) {
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
-                        } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().event_id != this->relation.event_id)
+                        } else if (msg.relations.references()) {
+                                if (msg.relations.references() != this->relation.event_id)
                                         return;
                         }
                         if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") &&
@@ -136,8 +136,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                         if (msg.transaction_id.has_value()) {
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
-                        } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().event_id != this->relation.event_id)
+                        } else if (msg.relations.references()) {
+                                if (msg.relations.references() != this->relation.event_id)
                                         return;
                         }
                         error_ = User;
@@ -152,8 +152,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                         if (msg.transaction_id.has_value()) {
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
-                        } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().event_id != this->relation.event_id)
+                        } else if (msg.relations.references()) {
+                                if (msg.relations.references() != this->relation.event_id)
                                         return;
                         }
 
@@ -217,8 +217,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                   if (msg.transaction_id.has_value()) {
                           if (msg.transaction_id.value() != this->transaction_id)
                                   return;
-                  } else if (msg.relates_to.has_value()) {
-                          if (msg.relates_to.value().event_id != this->relation.event_id)
+                  } else if (msg.relations.references()) {
+                          if (msg.relations.references() != this->relation.event_id)
                                   return;
                   }
 
@@ -385,8 +385,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                         if (msg.transaction_id.has_value()) {
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
-                        } else if ((msg.relates_to.has_value() && sender)) {
-                                if (msg.relates_to.value().event_id != this->relation.event_id)
+                        } else if (msg.relations.references()) {
+                                if (msg.relations.references() != this->relation.event_id)
                                         return;
                                 else {
                                         this->deviceId = QString::fromStdString(msg.from_device);
@@ -402,8 +402,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                         if (msg.transaction_id.has_value()) {
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
-                        } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().event_id != this->relation.event_id)
+                        } else if (msg.relations.references()) {
+                                if (msg.relations.references() != this->relation.event_id)
                                         return;
                         }
                         nhlog::ui()->info("Flow done on other side");
@@ -526,8 +526,8 @@ DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificati
         if (msg.transaction_id.has_value()) {
                 if (msg.transaction_id.value() != this->transaction_id)
                         return;
-        } else if (msg.relates_to.has_value()) {
-                if (msg.relates_to.value().event_id != this->relation.event_id)
+        } else if (msg.relations.references()) {
+                if (msg.relations.references() != this->relation.event_id)
                         return;
         }
         if ((std::find(msg.key_agreement_protocols.begin(),
@@ -625,8 +625,10 @@ DeviceVerificationFlow::startVerificationRequest()
                 req.transaction_id   = this->transaction_id;
                 this->canonical_json = nlohmann::json(req);
         } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
-                req.relates_to       = this->relation;
-                this->canonical_json = nlohmann::json(req);
+                req.relations.relations.push_back(this->relation);
+                // Set synthesized to surpress the nheko relation extensions
+                req.relations.synthesized = true;
+                this->canonical_json      = nlohmann::json(req);
         }
         send(req);
         setState(WaitingForOtherToAccept);
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 34b78962..6c613545 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -206,7 +206,7 @@ private:
         std::vector<int> sasList;
         UserKeyCache their_keys;
         TimelineModel *model_;
-        mtx::common::RelatesTo relation;
+        mtx::common::Relation relation;
 
         State state_ = PromptStartVerification;
         Error error_ = UnknownMethod;
@@ -230,8 +230,12 @@ private:
                                             static_cast<int>(err->status_code));
                           });
                 } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
-                        if constexpr (!std::is_same_v<T, mtx::events::msg::KeyVerificationRequest>)
-                                msg.relates_to = this->relation;
+                        if constexpr (!std::is_same_v<T,
+                                                      mtx::events::msg::KeyVerificationRequest>) {
+                                msg.relations.relations.push_back(this->relation);
+                                // Set synthesized to surpress the nheko relation extensions
+                                msg.relations.synthesized = true;
+                        }
                         (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type<T>);
                 }
 
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
index 3ae781f0..4218f491 100644
--- a/src/EventAccessors.cpp
+++ b/src/EventAccessors.cpp
@@ -250,31 +250,17 @@ struct EventFilesize
         }
 };
 
-struct EventInReplyTo
+struct EventRelations
 {
         template<class Content>
-        using related_ev_id_t = decltype(Content::relates_to.in_reply_to.event_id);
+        using related_ev_id_t = decltype(Content::relations);
         template<class T>
-        std::string operator()(const mtx::events::Event<T> &e)
+        mtx::common::Relations operator()(const mtx::events::Event<T> &e)
         {
                 if constexpr (is_detected<related_ev_id_t, T>::value) {
-                        return e.content.relates_to.in_reply_to.event_id;
+                        return e.content.relations;
                 }
-                return "";
-        }
-};
-
-struct EventRelatesTo
-{
-        template<class Content>
-        using related_ev_id_t = decltype(Content::relates_to.event_id);
-        template<class T>
-        std::string operator()(const mtx::events::Event<T> &e)
-        {
-                if constexpr (is_detected<related_ev_id_t, T>::value) {
-                        return e.content.relates_to.event_id;
-                }
-                return "";
+                return {};
         }
 };
 
@@ -434,15 +420,10 @@ mtx::accessors::mimetype(const mtx::events::collections::TimelineEvents &event)
 {
         return std::visit(EventMimeType{}, event);
 }
-std::string
-mtx::accessors::in_reply_to_event(const mtx::events::collections::TimelineEvents &event)
-{
-        return std::visit(EventInReplyTo{}, event);
-}
-std::string
-mtx::accessors::relates_to_event_id(const mtx::events::collections::TimelineEvents &event)
+mtx::common::Relations
+mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(EventRelatesTo{}, event);
+        return std::visit(EventRelations{}, event);
 }
 
 std::string
diff --git a/src/EventAccessors.h b/src/EventAccessors.h
index 0cdc5f89..60912497 100644
--- a/src/EventAccessors.h
+++ b/src/EventAccessors.h
@@ -53,10 +53,8 @@ std::string
 blurhash(const mtx::events::collections::TimelineEvents &event);
 std::string
 mimetype(const mtx::events::collections::TimelineEvents &event);
-std::string
-in_reply_to_event(const mtx::events::collections::TimelineEvents &event);
-std::string
-relates_to_event_id(const mtx::events::collections::TimelineEvents &event);
+mtx::common::Relations
+relations(const mtx::events::collections::TimelineEvents &event);
 std::string
 transaction_id(const mtx::events::collections::TimelineEvents &event);
 
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 4ccf8ab9..54be4751 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -575,29 +575,19 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
         if (!sendSessionTo.empty())
                 olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload);
 
-        mtx::common::ReplyRelatesTo relation;
-        mtx::common::RelatesTo r_relation;
-
         // relations shouldn't be encrypted...
-        if (body["content"].contains("m.relates_to")) {
-                if (body["content"]["m.relates_to"].contains("m.in_reply_to")) {
-                        relation = body["content"]["m.relates_to"];
-                } else if (body["content"]["m.relates_to"].contains("event_id")) {
-                        r_relation = body["content"]["m.relates_to"];
-                }
-        }
+        mtx::common::Relations relations = mtx::common::parse_relations(body["content"]);
 
         auto payload = olm::client()->encrypt_group_message(session.get(), body.dump());
 
         // Prepare the m.room.encrypted event.
         msg::Encrypted data;
-        data.ciphertext   = std::string((char *)payload.data(), payload.size());
-        data.sender_key   = olm::client()->identity_keys().curve25519;
-        data.session_id   = mtx::crypto::session_id(session.get());
-        data.device_id    = device_id;
-        data.algorithm    = MEGOLM_ALGO;
-        data.relates_to   = relation;
-        data.r_relates_to = r_relation;
+        data.ciphertext = std::string((char *)payload.data(), payload.size());
+        data.sender_key = olm::client()->identity_keys().curve25519;
+        data.session_id = mtx::crypto::session_id(session.get());
+        data.device_id  = device_id;
+        data.algorithm  = MEGOLM_ALGO;
+        data.relations  = relations;
 
         group_session_data.message_index = olm_outbound_group_session_message_index(session.get());
         nhlog::crypto()->debug("next message_index {}", group_session_data.message_index);
@@ -910,8 +900,7 @@ decryptEvent(const MegolmSessionIndex &index,
         body["unsigned"]         = event.unsigned_data;
 
         // relations are unencrypted in content...
-        if (json old_ev = event; old_ev["content"].count("m.relates_to") != 0)
-                body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"];
+        mtx::common::add_relations(body["content"], event.content.relations);
 
         mtx::events::collections::TimelineEvent te;
         try {
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index be4bc09e..4a90222f 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -293,16 +293,16 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
         }
 
         for (const auto &event : events.events) {
-                std::string relates_to;
+                std::set<std::string> relates_to;
                 if (auto redaction =
                       std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(
                         &event)) {
                         // fixup reactions
                         auto redacted = events_by_id_.object({room_id_, redaction->redacts});
                         if (redacted) {
-                                auto id = mtx::accessors::relates_to_event_id(*redacted);
-                                if (!id.empty()) {
-                                        auto idx = idToIndex(id);
+                                auto id = mtx::accessors::relations(*redacted);
+                                if (id.annotates()) {
+                                        auto idx = idToIndex(id.annotates()->event_id);
                                         if (idx) {
                                                 events_by_id_.remove(
                                                   {room_id_, redaction->redacts});
@@ -312,20 +312,17 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
                                 }
                         }
 
-                        relates_to = redaction->redacts;
-                } else if (auto reaction =
-                             std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
-                               &event)) {
-                        relates_to = reaction->content.relates_to.event_id;
+                        relates_to.insert(redaction->redacts);
                 } else {
-                        relates_to = mtx::accessors::in_reply_to_event(event);
+                        for (const auto &r : mtx::accessors::relations(event).relations)
+                                relates_to.insert(r.event_id);
                 }
 
-                if (!relates_to.empty()) {
-                        auto idx = cache::client()->getTimelineIndex(room_id_, relates_to);
+                for (const auto &relates_to_id : relates_to) {
+                        auto idx = cache::client()->getTimelineIndex(room_id_, relates_to_id);
                         if (idx) {
-                                events_by_id_.remove({room_id_, relates_to});
-                                decryptedEvents_.remove({room_id_, relates_to});
+                                events_by_id_.remove({room_id_, relates_to_id});
+                                decryptedEvents_.remove({room_id_, relates_to_id});
                                 events_.remove({room_id_, *idx});
                                 emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
                         }
@@ -430,13 +427,14 @@ EventStore::reactions(const std::string &event_id)
 
                 if (auto reaction = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
                       related_event);
-                    reaction && reaction->content.relates_to.key) {
-                        auto &agg = aggregation[reaction->content.relates_to.key.value()];
+                    reaction && reaction->content.relations.annotates() &&
+                    reaction->content.relations.annotates()->key) {
+                        auto key  = reaction->content.relations.annotates()->key.value();
+                        auto &agg = aggregation[key];
 
                         if (agg.count == 0) {
                                 Reaction temp{};
-                                temp.key_ =
-                                  QString::fromStdString(reaction->content.relates_to.key.value());
+                                temp.key_ = QString::fromStdString(key);
                                 reactions.push_back(temp);
                         }
 
@@ -691,8 +689,7 @@ EventStore::decryptEvent(const IdIndex &idx,
         body["unsigned"]         = e.unsigned_data;
 
         // relations are unencrypted in content...
-        if (json old_ev = e; old_ev["content"].count("m.relates_to") != 0)
-                body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"];
+        mtx::common::add_relations(body["content"], e.content.relations);
 
         json event_array = json::array();
         event_array.push_back(body);
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index b31c1f76..738fb37c 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -294,7 +294,8 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown)
                         text.formatted_body =
                           utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString();
 
-                text.relates_to.in_reply_to.event_id = related.related_event;
+                text.relations.relations.push_back(
+                  {mtx::common::RelationType::InReplyTo, related.related_event});
                 room->resetReply();
         }
 
@@ -316,7 +317,8 @@ InputBar::emote(QString msg)
         }
 
         if (!room->reply().isEmpty()) {
-                emote.relates_to.in_reply_to.event_id = room->reply().toStdString();
+                emote.relations.relations.push_back(
+                  {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
                 room->resetReply();
         }
 
@@ -346,7 +348,8 @@ InputBar::image(const QString &filename,
                 image.url = url.toStdString();
 
         if (!room->reply().isEmpty()) {
-                image.relates_to.in_reply_to.event_id = room->reply().toStdString();
+                image.relations.relations.push_back(
+                  {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
                 room->resetReply();
         }
 
@@ -371,7 +374,8 @@ InputBar::file(const QString &filename,
                 file.url = url.toStdString();
 
         if (!room->reply().isEmpty()) {
-                file.relates_to.in_reply_to.event_id = room->reply().toStdString();
+                file.relations.relations.push_back(
+                  {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
                 room->resetReply();
         }
 
@@ -397,7 +401,8 @@ InputBar::audio(const QString &filename,
                 audio.url = url.toStdString();
 
         if (!room->reply().isEmpty()) {
-                audio.relates_to.in_reply_to.event_id = room->reply().toStdString();
+                audio.relations.relations.push_back(
+                  {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
                 room->resetReply();
         }
 
@@ -422,7 +427,8 @@ InputBar::video(const QString &filename,
                 video.url = url.toStdString();
 
         if (!room->reply().isEmpty()) {
-                video.relates_to.in_reply_to.event_id = room->reply().toStdString();
+                video.relations.relations.push_back(
+                  {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
                 room->resetReply();
         }
 
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 968ec3c7..c47194f5 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -360,7 +360,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
                 const static QRegularExpression replyFallback(
                   "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
 
-                bool isReply = !in_reply_to_event(event).empty();
+                bool isReply = relations(event).reply_to().has_value();
 
                 auto formattedBody_ = QString::fromStdString(formatted_body(event));
                 if (formattedBody_.isEmpty()) {
@@ -442,7 +442,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
                 return cache::isRoomEncrypted(room_id_.toStdString());
         }
         case ReplyTo:
-                return QVariant(QString::fromStdString(in_reply_to_event(event)));
+                return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
         case Reactions: {
                 auto id = event_id(event);
                 return QVariant::fromValue(events.reactions(id));
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 9e045e83..e1e2b681 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -503,9 +503,11 @@ TimelineViewManager::queueReactionMessage(const QString &reactedEvent, const QSt
         // If selfReactedEvent is empty, that means we haven't previously reacted
         if (selfReactedEvent.isEmpty()) {
                 mtx::events::msg::Reaction reaction;
-                reaction.relates_to.rel_type = mtx::common::RelationType::Annotation;
-                reaction.relates_to.event_id = reactedEvent.toStdString();
-                reaction.relates_to.key      = reactionKey.toStdString();
+                mtx::common::Relation rel;
+                rel.rel_type = mtx::common::RelationType::Annotation;
+                rel.event_id = reactedEvent.toStdString();
+                rel.key      = reactionKey.toStdString();
+                reaction.relations.relations.push_back(rel);
 
                 timeline_->sendMessageEvent(reaction, mtx::events::EventType::Reaction);
                 // Otherwise, we have previously reacted and the reaction should be redacted