diff --git a/include/Deserializable.h b/include/Deserializable.h index 76290e1b..f4e8f425 100644 --- a/include/Deserializable.h +++ b/include/Deserializable.h @@ -47,3 +47,9 @@ public: { } }; + +class Serializable +{ +public: + virtual QJsonObject serialize() const = 0; +}; diff --git a/include/events/AliasesEventContent.h b/include/events/AliasesEventContent.h index a21aefd4..3adf8d46 100644 --- a/include/events/AliasesEventContent.h +++ b/include/events/AliasesEventContent.h @@ -26,10 +26,11 @@ namespace matrix { namespace events { -class AliasesEventContent : public Deserializable +class AliasesEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QList aliases() const; diff --git a/include/events/AvatarEventContent.h b/include/events/AvatarEventContent.h index fa6997e0..43be3122 100644 --- a/include/events/AvatarEventContent.h +++ b/include/events/AvatarEventContent.h @@ -30,10 +30,11 @@ namespace events * A picture that is associated with the room. */ -class AvatarEventContent : public Deserializable +class AvatarEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QUrl url() const; diff --git a/include/events/CanonicalAliasEventContent.h b/include/events/CanonicalAliasEventContent.h index 00df6207..aed7ea6b 100644 --- a/include/events/CanonicalAliasEventContent.h +++ b/include/events/CanonicalAliasEventContent.h @@ -32,10 +32,11 @@ namespace events * users which alias to use to advertise the room. */ -class CanonicalAliasEventContent : public Deserializable +class CanonicalAliasEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QString alias() const; diff --git a/include/events/CreateEventContent.h b/include/events/CreateEventContent.h index a0e40fb8..eedee9f1 100644 --- a/include/events/CreateEventContent.h +++ b/include/events/CreateEventContent.h @@ -29,10 +29,11 @@ namespace events * This is the first event in a room and cannot be changed. It acts as the root of all other events. */ -class CreateEventContent : public Deserializable +class CreateEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QString creator() const; diff --git a/include/events/Event.h b/include/events/Event.h index ea3eecea..af538644 100644 --- a/include/events/Event.h +++ b/include/events/Event.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include "Deserializable.h" @@ -58,13 +59,14 @@ bool isMessageEvent(EventType type); bool isStateEvent(EventType type); template -class Event : public Deserializable +class Event : public Deserializable, public Serializable { public: inline Content content() const; inline EventType eventType() const; void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; private: Content content_; @@ -92,6 +94,56 @@ void Event::deserialize(const QJsonValue &data) auto object = data.toObject(); content_.deserialize(object.value("content")); + type_ = extractEventType(object); +} + +template +QJsonObject Event::serialize() const +{ + QJsonObject object; + + switch (type_) { + case EventType::RoomAliases: + object["type"] = "m.room.aliases"; + break; + case EventType::RoomAvatar: + object["type"] = "m.room.avatar"; + break; + case EventType::RoomCanonicalAlias: + object["type"] = "m.room.canonical_alias"; + break; + case EventType::RoomCreate: + object["type"] = "m.room.create"; + break; + case EventType::RoomHistoryVisibility: + object["type"] = "m.room.history_visibility"; + break; + case EventType::RoomJoinRules: + object["type"] = "m.room.join_rules"; + break; + case EventType::RoomMember: + object["type"] = "m.room.member"; + break; + case EventType::RoomMessage: + object["type"] = "m.room.message"; + break; + case EventType::RoomName: + object["type"] = "m.room.name"; + break; + case EventType::RoomPowerLevels: + object["type"] = "m.room.power_levels"; + break; + case EventType::RoomTopic: + object["type"] = "m.room.topic"; + break; + case EventType::Unsupported: + qWarning() << "Unsupported type to serialize"; + break; + } + + object["content"] = content_.serialize(); + + return object; } } // namespace events } // namespace matrix diff --git a/include/events/HistoryVisibilityEventContent.h b/include/events/HistoryVisibilityEventContent.h index 1df83d09..3edc7ce5 100644 --- a/include/events/HistoryVisibilityEventContent.h +++ b/include/events/HistoryVisibilityEventContent.h @@ -32,12 +32,13 @@ enum class HistoryVisibility { WorldReadable, }; -class HistoryVisibilityEventContent : public Deserializable +class HistoryVisibilityEventContent : public Deserializable, public Serializable { public: inline HistoryVisibility historyVisibility() const; void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; private: HistoryVisibility history_visibility_; diff --git a/include/events/JoinRulesEventContent.h b/include/events/JoinRulesEventContent.h index 746188e4..9b07e9a6 100644 --- a/include/events/JoinRulesEventContent.h +++ b/include/events/JoinRulesEventContent.h @@ -44,10 +44,11 @@ enum class JoinRule { * Describes how users are allowed to join the room. */ -class JoinRulesEventContent : public Deserializable +class JoinRulesEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline JoinRule joinRule() const; diff --git a/include/events/MemberEventContent.h b/include/events/MemberEventContent.h index e61d0cda..feaf1559 100644 --- a/include/events/MemberEventContent.h +++ b/include/events/MemberEventContent.h @@ -47,10 +47,11 @@ enum class Membership { * The current membership state of a user in the room. */ -class MemberEventContent : public Deserializable +class MemberEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QUrl avatarUrl() const; inline QString displayName() const; diff --git a/include/events/MessageEventContent.h b/include/events/MessageEventContent.h index a9d7e470..3bfd11c2 100644 --- a/include/events/MessageEventContent.h +++ b/include/events/MessageEventContent.h @@ -56,10 +56,11 @@ enum class MessageEventType { MessageEventType extractMessageEventType(const QJsonObject &data); -class MessageEventContent : public Deserializable +class MessageEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QString body() const; diff --git a/include/events/NameEventContent.h b/include/events/NameEventContent.h index bf5a9209..ab90fd23 100644 --- a/include/events/NameEventContent.h +++ b/include/events/NameEventContent.h @@ -29,10 +29,11 @@ namespace events * A human-friendly room name designed to be displayed to the end-user. */ -class NameEventContent : public Deserializable +class NameEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QString name() const; diff --git a/include/events/PowerLevelsEventContent.h b/include/events/PowerLevelsEventContent.h index e5762812..7def9800 100644 --- a/include/events/PowerLevelsEventContent.h +++ b/include/events/PowerLevelsEventContent.h @@ -36,10 +36,11 @@ enum class PowerLevels { * Defines the power levels (privileges) of users in the room. */ -class PowerLevelsEventContent : public Deserializable +class PowerLevelsEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline int banLevel() const; inline int inviteLevel() const; diff --git a/include/events/RoomEvent.h b/include/events/RoomEvent.h index b73d5ce9..79fc4be2 100644 --- a/include/events/RoomEvent.h +++ b/include/events/RoomEvent.h @@ -36,6 +36,7 @@ public: inline uint64_t timestamp() const; void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; private: QString event_id_; @@ -94,5 +95,18 @@ void RoomEvent::deserialize(const QJsonValue &data) sender_ = object.value("sender").toString(); origin_server_ts_ = object.value("origin_server_ts").toDouble(); } + +template +QJsonObject RoomEvent::serialize() const +{ + QJsonObject object = Event::serialize(); + + object["event_id"] = event_id_; + object["room_id"] = room_id_; + object["sender"] = sender_; + object["origin_server_ts"] = QJsonValue(static_cast(origin_server_ts_)); + + return object; +} } // namespace events } // namespace matrix diff --git a/include/events/StateEvent.h b/include/events/StateEvent.h index 2075a996..26313048 100644 --- a/include/events/StateEvent.h +++ b/include/events/StateEvent.h @@ -33,6 +33,7 @@ public: inline Content previousContent() const; void deserialize(const QJsonValue &data); + QJsonObject serialize() const; private: QString state_key_; @@ -66,5 +67,20 @@ void StateEvent::deserialize(const QJsonValue &data) if (object.contains("prev_content")) prev_content_.deserialize(object.value("prev_content")); } + +template +QJsonObject StateEvent::serialize() const +{ + QJsonObject object = RoomEvent::serialize(); + + object["state_key"] = state_key_; + + auto prev = prev_content_.serialize(); + + if (!prev.isEmpty()) + object["prev_content"] = prev; + + return object; +} } // namespace events } // namespace matrix diff --git a/include/events/TopicEventContent.h b/include/events/TopicEventContent.h index 460d019e..ff2fe3bb 100644 --- a/include/events/TopicEventContent.h +++ b/include/events/TopicEventContent.h @@ -29,10 +29,11 @@ namespace events * A topic is a short message detailing what is currently being discussed in the room. */ -class TopicEventContent : public Deserializable +class TopicEventContent : public Deserializable, public Serializable { public: void deserialize(const QJsonValue &data) override; + QJsonObject serialize() const override; inline QString topic() const; diff --git a/src/events/AliasesEventContent.cc b/src/events/AliasesEventContent.cc index 89754315..899a7182 100644 --- a/src/events/AliasesEventContent.cc +++ b/src/events/AliasesEventContent.cc @@ -36,3 +36,18 @@ void AliasesEventContent::deserialize(const QJsonValue &data) for (const auto &alias : aliases) aliases_.push_back(alias.toString()); } + +QJsonObject AliasesEventContent::serialize() const +{ + QJsonObject object; + + QJsonArray aliases; + + for (const auto &alias : aliases_) + aliases.push_back(alias); + + if (aliases.size() > 0) + object["aliases"] = aliases; + + return object; +} diff --git a/src/events/AvatarEventContent.cc b/src/events/AvatarEventContent.cc index 2f88cac2..dfd46f68 100644 --- a/src/events/AvatarEventContent.cc +++ b/src/events/AvatarEventContent.cc @@ -36,3 +36,13 @@ void AvatarEventContent::deserialize(const QJsonValue &data) if (!url_.isValid()) qWarning() << "Invalid avatar url" << url_; } + +QJsonObject AvatarEventContent::serialize() const +{ + QJsonObject object; + + if (!url_.isEmpty()) + object["url"] = url_.toString(); + + return object; +} diff --git a/src/events/CanonicalAliasEventContent.cc b/src/events/CanonicalAliasEventContent.cc index 89042722..9792027c 100644 --- a/src/events/CanonicalAliasEventContent.cc +++ b/src/events/CanonicalAliasEventContent.cc @@ -31,3 +31,13 @@ void CanonicalAliasEventContent::deserialize(const QJsonValue &data) alias_ = object.value("alias").toString(); } + +QJsonObject CanonicalAliasEventContent::serialize() const +{ + QJsonObject object; + + if (!alias_.isEmpty()) + object["alias"] = alias_; + + return object; +} diff --git a/src/events/CreateEventContent.cc b/src/events/CreateEventContent.cc index d43a4cc5..a7d22791 100644 --- a/src/events/CreateEventContent.cc +++ b/src/events/CreateEventContent.cc @@ -31,3 +31,13 @@ void CreateEventContent::deserialize(const QJsonValue &data) creator_ = object.value("creator").toString(); } + +QJsonObject CreateEventContent::serialize() const +{ + QJsonObject object; + + if (!creator_.isEmpty()) + object["creator"] = creator_; + + return object; +} diff --git a/src/events/HistoryVisibilityEventContent.cc b/src/events/HistoryVisibilityEventContent.cc index 25630aa4..77970553 100644 --- a/src/events/HistoryVisibilityEventContent.cc +++ b/src/events/HistoryVisibilityEventContent.cc @@ -42,3 +42,19 @@ void HistoryVisibilityEventContent::deserialize(const QJsonValue &data) else throw DeserializationException(QString("Unknown history_visibility value: %1").arg(value).toUtf8().constData()); } + +QJsonObject HistoryVisibilityEventContent::serialize() const +{ + QJsonObject object; + + if (history_visibility_ == HistoryVisibility::Invited) + object["history_visibility"] = "invited"; + else if (history_visibility_ == HistoryVisibility::Joined) + object["history_visibility"] = "joined"; + else if (history_visibility_ == HistoryVisibility::Shared) + object["history_visibility"] = "shared"; + else if (history_visibility_ == HistoryVisibility::WorldReadable) + object["history_visibility"] = "world_readable"; + + return object; +} diff --git a/src/events/JoinRulesEventContent.cc b/src/events/JoinRulesEventContent.cc index 913aa097..389ab9b8 100644 --- a/src/events/JoinRulesEventContent.cc +++ b/src/events/JoinRulesEventContent.cc @@ -42,3 +42,19 @@ void JoinRulesEventContent::deserialize(const QJsonValue &data) else throw DeserializationException(QString("Unknown join_rule value: %1").arg(value).toUtf8().constData()); } + +QJsonObject JoinRulesEventContent::serialize() const +{ + QJsonObject object; + + if (join_rule_ == JoinRule::Invite) + object["join_rule"] = "invite"; + else if (join_rule_ == JoinRule::Knock) + object["join_rule"] = "knock"; + else if (join_rule_ == JoinRule::Private) + object["join_rule"] = "private"; + else if (join_rule_ == JoinRule::Public) + object["join_rule"] = "public"; + + return object; +} diff --git a/src/events/MemberEventContent.cc b/src/events/MemberEventContent.cc index 4dc8ad5f..608ad1b2 100644 --- a/src/events/MemberEventContent.cc +++ b/src/events/MemberEventContent.cc @@ -55,3 +55,27 @@ void MemberEventContent::deserialize(const QJsonValue &data) if (object.contains("displayname")) display_name_ = object.value("displayname").toString(); } + +QJsonObject MemberEventContent::serialize() const +{ + QJsonObject object; + + if (membership_state_ == Membership::Ban) + object["membership"] = "ban"; + else if (membership_state_ == Membership::Invite) + object["membership"] = "invite"; + else if (membership_state_ == Membership::Join) + object["membership"] = "join"; + else if (membership_state_ == Membership::Knock) + object["membership"] = "knock"; + else if (membership_state_ == Membership::Leave) + object["membership"] = "leave"; + + if (!avatar_url_.isEmpty()) + object["avatar_url"] = avatar_url_.toString(); + + if (!display_name_.isEmpty()) + object["displayname"] = display_name_; + + return object; +} diff --git a/src/events/MessageEventContent.cc b/src/events/MessageEventContent.cc index df2c39e8..215f2d97 100644 --- a/src/events/MessageEventContent.cc +++ b/src/events/MessageEventContent.cc @@ -61,3 +61,11 @@ void MessageEventContent::deserialize(const QJsonValue &data) body_ = object.value("body").toString(); } + +QJsonObject MessageEventContent::serialize() const +{ + // TODO: Add for all the message contents. + QJsonObject object; + + return object; +} diff --git a/src/events/NameEventContent.cc b/src/events/NameEventContent.cc index bfb2f878..61126e6b 100644 --- a/src/events/NameEventContent.cc +++ b/src/events/NameEventContent.cc @@ -31,3 +31,13 @@ void NameEventContent::deserialize(const QJsonValue &data) name_ = object.value("name").toString(); } + +QJsonObject NameEventContent::serialize() const +{ + QJsonObject object; + + if (!name_.isEmpty()) + object["name"] = name_; + + return object; +} diff --git a/src/events/PowerLevelsEventContent.cc b/src/events/PowerLevelsEventContent.cc index 048b8e97..765913c4 100644 --- a/src/events/PowerLevelsEventContent.cc +++ b/src/events/PowerLevelsEventContent.cc @@ -65,6 +65,34 @@ void PowerLevelsEventContent::deserialize(const QJsonValue &data) } } +QJsonObject PowerLevelsEventContent::serialize() const +{ + QJsonObject object; + + object["ban"] = ban_; + object["invite"] = invite_; + object["kick"] = kick_; + object["redact"] = redact_; + + object["events_default"] = events_default_; + object["users_default"] = users_default_; + object["state_default"] = state_default_; + + QJsonObject users; + QJsonObject events; + + for (auto it = users_.constBegin(); it != users_.constEnd(); it++) + users.insert(it.key(), it.value()); + + for (auto it = events_.constBegin(); it != events_.constEnd(); it++) + events.insert(it.key(), it.value()); + + object["users"] = users; + object["events"] = events; + + return object; +} + int PowerLevelsEventContent::eventLevel(QString event_type) const { if (events_.contains(event_type)) diff --git a/src/events/TopicEventContent.cc b/src/events/TopicEventContent.cc index e8d99ee9..5acdba3e 100644 --- a/src/events/TopicEventContent.cc +++ b/src/events/TopicEventContent.cc @@ -31,3 +31,13 @@ void TopicEventContent::deserialize(const QJsonValue &data) topic_ = object.value("topic").toString(); } + +QJsonObject TopicEventContent::serialize() const +{ + QJsonObject object; + + if (!topic_.isEmpty()) + object["topic"] = topic_; + + return object; +} diff --git a/tests/events.cc b/tests/events.cc index 3c5ed536..7712255c 100644 --- a/tests/events.cc +++ b/tests/events.cc @@ -1,4 +1,5 @@ #include +#include #include #include "Event.h" @@ -28,6 +29,7 @@ TEST(BaseEvent, Deserialization) Event name_event; name_event.deserialize(data); EXPECT_EQ(name_event.content().name(), "Room Name"); + EXPECT_EQ(name_event.serialize(), data); // TopicEventContent data = QJsonObject{ @@ -37,6 +39,7 @@ TEST(BaseEvent, Deserialization) Event topic_event; topic_event.deserialize(data); EXPECT_EQ(topic_event.content().topic(), "Room Topic"); + EXPECT_EQ(topic_event.serialize(), data); // AvatarEventContent data = QJsonObject{ @@ -46,6 +49,7 @@ TEST(BaseEvent, Deserialization) Event avatar_event; avatar_event.deserialize(data); EXPECT_EQ(avatar_event.content().url().toString(), "https://matrix.org"); + EXPECT_EQ(avatar_event.serialize(), data); // AliasesEventContent data = QJsonObject{ @@ -55,6 +59,7 @@ TEST(BaseEvent, Deserialization) Event aliases_event; aliases_event.deserialize(data); EXPECT_EQ(aliases_event.content().aliases().size(), 2); + EXPECT_EQ(aliases_event.serialize(), data); // CreateEventContent data = QJsonObject{ @@ -64,6 +69,7 @@ TEST(BaseEvent, Deserialization) Event create_event; create_event.deserialize(data); EXPECT_EQ(create_event.content().creator(), "@alice:matrix.org"); + EXPECT_EQ(create_event.serialize(), data); // JoinRulesEventContent data = QJsonObject{ @@ -73,6 +79,7 @@ TEST(BaseEvent, Deserialization) Event join_rules_event; join_rules_event.deserialize(data); EXPECT_EQ(join_rules_event.content().joinRule(), JoinRule::Private); + EXPECT_EQ(join_rules_event.serialize(), data); } TEST(BaseEvent, DeserializationException) @@ -110,6 +117,7 @@ TEST(RoomEvent, Deserialization) EXPECT_EQ(event.sender(), "@alice:matrix.org"); EXPECT_EQ(event.timestamp(), 1323238293289323); EXPECT_EQ(event.content().name(), "Name"); + EXPECT_EQ(event.serialize(), data); } TEST(RoomEvent, DeserializationException) @@ -152,6 +160,7 @@ TEST(StateEvent, Deserialization) EXPECT_EQ(event.content().name(), "Name"); EXPECT_EQ(event.stateKey(), "some_state_key"); EXPECT_EQ(event.previousContent().name(), "Previous Name"); + EXPECT_EQ(event.serialize(), data); } TEST(StateEvent, DeserializationException) @@ -199,6 +208,7 @@ TEST(AliasesEventContent, Deserialization) content.deserialize(data); EXPECT_EQ(content.aliases().size(), 2); + EXPECT_EQ(content.serialize(), data); } TEST(AliasesEventContent, NotAnObject) @@ -232,6 +242,7 @@ TEST(AvatarEventContent, Deserialization) content.deserialize(data); EXPECT_EQ(content.url().toString(), "https://matrix.org/avatar.png"); + EXPECT_EQ(content.serialize(), data); } TEST(AvatarEventContent, NotAnObject) @@ -264,6 +275,7 @@ TEST(CreateEventContent, Deserialization) content.deserialize(data); EXPECT_EQ(content.creator(), "@alice:matrix.org"); + EXPECT_EQ(content.serialize(), data); } TEST(CreateEventContent, NotAnObject) @@ -419,6 +431,7 @@ TEST(CanonicalAliasEventContent, Deserialization) content.deserialize(data); EXPECT_EQ(content.alias(), "Room Alias"); + EXPECT_EQ(content.serialize(), data); } TEST(CanonicalAliasEventContent, NotAnObject) @@ -521,6 +534,7 @@ TEST(NameEventContent, Deserialization) content.deserialize(data); EXPECT_EQ(content.name(), "Room Name"); + EXPECT_EQ(content.serialize(), data); } TEST(NameEventContent, NotAnObject) @@ -599,6 +613,8 @@ TEST(PowerLevelsEventContent, FullDeserialization) EXPECT_EQ(power_levels.eventLevel("m.message.text"), 8); EXPECT_EQ(power_levels.eventLevel("m.message.image"), 9); EXPECT_EQ(power_levels.eventLevel("m.message.gif"), 5); + + EXPECT_EQ(power_levels.serialize(), data); } TEST(PowerLevelsEventContent, PartialDeserialization) @@ -651,6 +667,7 @@ TEST(TopicEventContent, Deserialization) content.deserialize(data); EXPECT_EQ(content.topic(), "Room Topic"); + EXPECT_EQ(content.serialize(), data); } TEST(TopicEventContent, NotAnObject)