Skip to content
Snippets Groups Projects
Verified Commit 5ef4460c authored by Nicolas Werner's avatar Nicolas Werner
Browse files

Add threading support

parent eceabb6b
No related branches found
No related tags found
No related merge requests found
Pipeline #3732 passed
......@@ -163,6 +163,8 @@ enum class RelationType
Replace,
//! im.nheko.relations.v1.in_reply_to rel_type
InReplyTo,
//! m.thread
Thread,
//! not one of the supported types
Unsupported
};
......@@ -183,6 +185,9 @@ struct Relation
//! key is the reaction itself
std::optional<std::string> key = std::nullopt;
//! proprietary field to track if this is a fallback for something else
bool is_fallback = false;
friend void from_json(const nlohmann::json &obj, Relation &relation);
friend void to_json(nlohmann::json &obj, const Relation &relation);
};
......@@ -196,10 +201,11 @@ struct Relations
//! im.nheko.relactions.v1.relations
bool synthesized = false;
std::optional<std::string> reply_to() const;
std::optional<std::string> replaces() const;
std::optional<std::string> references() const;
std::optional<Relation> annotates() const;
std::optional<std::string> reply_to(bool include_fallback = true) const;
std::optional<std::string> replaces(bool include_fallback = true) const;
std::optional<std::string> references(bool include_fallback = true) const;
std::optional<std::string> thread(bool include_fallback = true) const;
std::optional<Relation> annotates(bool include_fallback = true) const;
};
/// @brief Parses relations from a content object
......
......@@ -185,6 +185,9 @@ to_json(json &obj, const RelationType &type)
case RelationType::InReplyTo:
obj = "im.nheko.relations.v1.in_reply_to";
break;
case RelationType::Thread:
obj = "m.thread";
break;
case RelationType::Unsupported:
default:
obj = "unsupported";
......@@ -203,6 +206,8 @@ from_json(const json &obj, RelationType &type)
type = RelationType::Replace;
else if (obj.get<std::string>() == "im.nheko.relations.v1.in_reply_to")
type = RelationType::InReplyTo;
else if (obj.get<std::string>() == "m.thread")
type = RelationType::Thread;
else
type = RelationType::Unsupported;
}
......@@ -218,18 +223,27 @@ parse_relations(const nlohmann::json &content)
rels.synthesized = false;
return rels;
} else if (content.contains("m.relates_to")) {
if (content.at("m.relates_to").contains("m.in_reply_to")) {
const auto &relates_to = content.at("m.relates_to");
if (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<std::string>();
r.event_id = relates_to.at("m.in_reply_to").at("event_id").get<std::string>();
r.rel_type = RelationType::InReplyTo;
Relations rels;
if (auto thread_type = relates_to.find("rel_type");
thread_type != relates_to.end() && *thread_type == "m.thread") {
if (auto thread_id = relates_to.find("event_id");
thread_id != relates_to.end()) {
r.is_fallback = relates_to.value("is_falling_back", false);
rels.relations.push_back(relates_to.get<mtx::common::Relation>());
}
}
rels.relations.push_back(r);
rels.synthesized = true;
return rels;
} else {
Relation r = content.at("m.relates_to").get<mtx::common::Relation>();
Relation r = relates_to.get<mtx::common::Relation>();
Relations rels;
rels.relations.push_back(r);
rels.synthesized = true;
......@@ -262,20 +276,24 @@ add_relations(nlohmann::json &content, const Relations &relations)
if (relations.relations.empty())
return;
std::optional<Relation> edit, not_edit;
std::optional<Relation> edit, not_edit, reply;
for (const auto &r : relations.relations) {
if (r.rel_type == RelationType::Replace)
edit = r;
else if (r.rel_type == RelationType::InReplyTo)
reply = 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;
}
content["m.relates_to"] = *not_edit;
}
if (reply) {
content["m.relates_to"]["m.in_reply_to"]["event_id"] = reply->event_id;
if (reply->is_fallback && not_edit && not_edit->rel_type == RelationType::Thread)
content["m.relates_to"]["is_falling_back"] = true;
}
if (edit) {
......@@ -316,12 +334,14 @@ apply_relations(nlohmann::json &content, const Relations &relations)
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<RelationType>();
if (obj.find("event_id") != obj.end())
relates_to.event_id = obj.at("event_id").get<std::string>();
if (obj.find("key") != obj.end())
relates_to.key = obj.at("key").get<std::string>();
if (auto it = obj.find("rel_type"); it != obj.end())
relates_to.rel_type = it->get<RelationType>();
if (auto it = obj.find("event_id"); it != obj.end())
relates_to.event_id = it->get<std::string>();
if (auto it = obj.find("key"); it != obj.end())
relates_to.key = it->get<std::string>();
if (auto it = obj.find("im.nheko.relations.v1.is_fallback"); it != obj.end())
relates_to.is_fallback = it->get<bool>();
}
void
......@@ -331,36 +351,43 @@ to_json(json &obj, const Relation &relates_to)
obj["event_id"] = relates_to.event_id;
if (relates_to.key.has_value())
obj["key"] = relates_to.key.value();
if (relates_to.is_fallback)
obj["im.nheko.relations.v1.is_fallback"] = true;
}
static inline std::optional<std::string>
return_first_relation_matching(RelationType t, const Relations &rels)
return_first_relation_matching(RelationType t, const Relations &rels, bool include_fallback)
{
for (const auto &r : rels.relations)
if (r.rel_type == t)
if (r.rel_type == t && (include_fallback || r.is_fallback == false))
return r.event_id;
return std::nullopt;
}
std::optional<std::string>
Relations::reply_to() const
Relations::reply_to(bool include_fallback) const
{
return return_first_relation_matching(RelationType::InReplyTo, *this, include_fallback);
}
std::optional<std::string>
Relations::replaces(bool include_fallback) const
{
return return_first_relation_matching(RelationType::InReplyTo, *this);
return return_first_relation_matching(RelationType::Replace, *this, include_fallback);
}
std::optional<std::string>
Relations::replaces() const
Relations::references(bool include_fallback) const
{
return return_first_relation_matching(RelationType::Replace, *this);
return return_first_relation_matching(RelationType::Reference, *this, include_fallback);
}
std::optional<std::string>
Relations::references() const
Relations::thread(bool include_fallback) const
{
return return_first_relation_matching(RelationType::Reference, *this);
return return_first_relation_matching(RelationType::Thread, *this, include_fallback);
}
std::optional<Relation>
Relations::annotates() const
Relations::annotates(bool include_fallback) const
{
for (const auto &r : relations)
if (r.rel_type == RelationType::Annotation)
if (r.rel_type == RelationType::Annotation && (include_fallback || r.is_fallback == false))
return r;
return std::nullopt;
}
......
......@@ -768,3 +768,48 @@ TEST(RoomEvents, Encrypted)
EXPECT_EQ(data, json(event));
}
TEST(RoomEvents, ThreadedMessage)
{
json data = R"({
"origin_server_ts": 1510489356530,
"sender": "@nheko_test:matrix.org",
"event_id": "$15104893562785758wEgEU:matrix.org",
"unsigned": {
"age": 2225,
"transaction_id": "m1510489356267.2"
},
"content": {
"body": "hey there",
"msgtype": "m.text",
"m.relates_to": {
"rel_type": "m.thread",
"event_id": "$root",
"m.in_reply_to": {
"event_id": "$target"
},
"is_falling_back": true
}
},
"type": "m.room.message",
"room_id": "!lfoDRlNFWlvOnvkBwQ:matrix.org"
})"_json;
RoomEvent<msg::Text> event = data.get<RoomEvent<msg::Text>>();
EXPECT_EQ(event.type, EventType::RoomMessage);
EXPECT_EQ(event.event_id, "$15104893562785758wEgEU:matrix.org");
EXPECT_EQ(event.room_id, "!lfoDRlNFWlvOnvkBwQ:matrix.org");
EXPECT_EQ(event.sender, "@nheko_test:matrix.org");
EXPECT_EQ(event.origin_server_ts, 1510489356530L);
EXPECT_EQ(event.unsigned_data.age, 2225);
EXPECT_EQ(event.unsigned_data.transaction_id, "m1510489356267.2");
EXPECT_EQ(event.content.body, "hey there");
EXPECT_EQ(event.content.msgtype, "m.text");
EXPECT_EQ(event.content.relations.reply_to(), "$target");
EXPECT_EQ(event.content.relations.reply_to(false), std::nullopt);
EXPECT_EQ(event.content.relations.thread(), "$root");
EXPECT_EQ(data.dump(), json(event).dump());
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment