summary refs log tree commit diff
diff options
context:
space:
mode:
-rwxr-xr-x.ci/linux/gtest.sh19
-rwxr-xr-x.ci/linux/run-tests.sh7
-rw-r--r--.travis.yml3
-rw-r--r--CMakeLists.txt58
-rw-r--r--Makefile9
-rw-r--r--include/ChatPage.h71
-rw-r--r--include/MatrixClient.h15
-rw-r--r--include/RoomState.h96
-rw-r--r--include/Sync.h131
-rw-r--r--include/TextInputWidget.h3
-rw-r--r--include/events/AliasesEventContent.h42
-rw-r--r--include/events/AvatarEventContent.h46
-rw-r--r--include/events/CanonicalAliasEventContent.h48
-rw-r--r--include/events/CreateEventContent.h47
-rw-r--r--include/events/Event.h183
-rw-r--r--include/events/HistoryVisibilityEventContent.h49
-rw-r--r--include/events/JoinRulesEventContent.h61
-rw-r--r--include/events/MemberEventContent.h68
-rw-r--r--include/events/MessageEvent.h64
-rw-r--r--include/events/MessageEventContent.h74
-rw-r--r--include/events/NameEventContent.h45
-rw-r--r--include/events/PowerLevelsEventContent.h73
-rw-r--r--include/events/RoomEvent.h116
-rw-r--r--include/events/StateEvent.h88
-rw-r--r--include/events/TopicEventContent.h46
-rw-r--r--include/events/messages/Audio.h50
-rw-r--r--include/events/messages/Emote.h34
-rw-r--r--include/events/messages/File.h55
-rw-r--r--include/events/messages/Image.h54
-rw-r--r--include/events/messages/Location.h50
-rw-r--r--include/events/messages/Notice.h34
-rw-r--r--include/events/messages/Text.h34
-rw-r--r--include/events/messages/Video.h55
-rw-r--r--include/timeline/TimelineItem.h43
-rw-r--r--include/timeline/TimelineView.h86
-rw-r--r--include/timeline/TimelineViewManager.h10
-rw-r--r--include/timeline/widgets/AudioItem.h9
-rw-r--r--include/timeline/widgets/FileItem.h11
-rw-r--r--include/timeline/widgets/ImageItem.h11
-rw-r--r--include/timeline/widgets/VideoItem.h9
m---------libs/matrix-structs0
-rw-r--r--src/Cache.cc65
-rw-r--r--src/ChatPage.cc280
-rw-r--r--src/MatrixClient.cc56
-rw-r--r--src/RoomList.cc1
-rw-r--r--src/RoomState.cc316
-rw-r--r--src/Sync.cc307
-rw-r--r--src/events/AliasesEventContent.cc55
-rw-r--r--src/events/AvatarEventContent.cc50
-rw-r--r--src/events/CanonicalAliasEventContent.cc45
-rw-r--r--src/events/CreateEventContent.cc45
-rw-r--r--src/events/Event.cc106
-rw-r--r--src/events/HistoryVisibilityEventContent.cc64
-rw-r--r--src/events/JoinRulesEventContent.cc63
-rw-r--r--src/events/MemberEventContent.cc84
-rw-r--r--src/events/MessageEventContent.cc74
-rw-r--r--src/events/NameEventContent.cc45
-rw-r--r--src/events/PowerLevelsEventContent.cc114
-rw-r--r--src/events/TopicEventContent.cc45
-rw-r--r--src/events/messages/Audio.cc40
-rw-r--r--src/events/messages/Emote.cc27
-rw-r--r--src/events/messages/File.cc50
-rw-r--r--src/events/messages/Image.cc52
-rw-r--r--src/events/messages/Location.cc47
-rw-r--r--src/events/messages/Notice.cc27
-rw-r--r--src/events/messages/Text.cc27
-rw-r--r--src/events/messages/Video.cc53
-rw-r--r--src/timeline/TimelineItem.cc79
-rw-r--r--src/timeline/TimelineView.cc200
-rw-r--r--src/timeline/TimelineViewManager.cc29
-rw-r--r--src/timeline/widgets/AudioItem.cc15
-rw-r--r--src/timeline/widgets/FileItem.cc15
-rw-r--r--src/timeline/widgets/ImageItem.cc13
-rw-r--r--src/timeline/widgets/VideoItem.cc11
-rw-r--r--tests/event_collection.cc111
-rw-r--r--tests/events.cc707
-rw-r--r--tests/message_events.cc287
77 files changed, 649 insertions, 4963 deletions
diff --git a/.ci/linux/gtest.sh b/.ci/linux/gtest.sh
deleted file mode 100755
index 8dd7084c..00000000
--- a/.ci/linux/gtest.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-
-set -evx
-
-sudo apt-get -qq update
-sudo apt-get install -y libgtest-dev
-wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz
-tar xf release-1.8.0.tar.gz
-cd googletest-release-1.8.0
-
-cmake -DBUILD_SHARED_LIBS=ON .
-make
-sudo cp -a googletest/include/gtest /usr/include
-sudo cp -a googlemock/gtest/*.so /usr/lib/
-
-sudo ldconfig -v | grep gtest
-
-cd $TRAVIS_BUILD_DIR
-
diff --git a/.ci/linux/run-tests.sh b/.ci/linux/run-tests.sh
deleted file mode 100755
index dc9e303c..00000000
--- a/.ci/linux/run-tests.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-set -evx
-
-cmake -DBUILD_TESTS=ON -H. -Bbuild && cmake --build build
-
-cd build && GTEST_COLOR=1 ctest --verbose
diff --git a/.travis.yml b/.travis.yml
index c0f0ed16..ba45a88b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,6 @@ matrix:
           compiler: gcc
           env:
             - COMPILER=g++-6
-            - RUN_TESTS=1
           addons:
             apt:
               sources: ['ubuntu-toolchain-r-test']
@@ -30,7 +29,6 @@ matrix:
 
 before_install:
     - export CXX=${COMPILER}
-    - if [ $TRAVIS_OS_NAME == linux ]; then ./.ci/linux/gtest.sh; fi
 
 install:
     - if [ $TRAVIS_OS_NAME == osx ]; then brew update && brew install qt5 lmdb clang-format; fi
@@ -45,7 +43,6 @@ script:
     - make -C build -j2
     - if [ $TRAVIS_OS_NAME == osx ]; then make lint; fi
     - if [ $TRAVIS_OS_NAME == osx ]; then ./.ci/macos/deploy.sh; fi
-    - if [ $RUN_TESTS == 1 ]; then ./.ci/linux/run-tests.sh; fi
     - if [ $TRAVIS_OS_NAME == linux ]; then ./.ci/linux/deploy.sh; fi
 
 deploy:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d518fe6..69efeedd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,5 @@
 cmake_minimum_required(VERSION 3.1)
 
-option(BUILD_TESTS "Build all tests" OFF)
 option(APPVEYOR_BUILD "Build on appveyor" OFF)
 
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
@@ -122,6 +121,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
         -Werror \
         -pipe \
         -pedantic \
+        -ferror-limit=3 \
         -Wunreachable-code")
 
     if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
@@ -200,7 +200,6 @@ set(SRC_FILES
     src/RoomState.cc
     src/SideBarActions.cc
     src/Splitter.cc
-    src/Sync.cc
     src/TextInputWidget.cc
     src/TopRoomBar.cc
     src/TrayIcon.cc
@@ -211,35 +210,8 @@ set(SRC_FILES
     src/main.cc
 )
 
-set(MATRIX_EVENTS
-    src/events/Event.cc
-
-    src/events/AliasesEventContent.cc
-    src/events/AvatarEventContent.cc
-    src/events/CanonicalAliasEventContent.cc
-    src/events/CreateEventContent.cc
-    src/events/HistoryVisibilityEventContent.cc
-    src/events/JoinRulesEventContent.cc
-    src/events/MemberEventContent.cc
-    src/events/MessageEventContent.cc
-    src/events/NameEventContent.cc
-    src/events/PowerLevelsEventContent.cc
-    src/events/TopicEventContent.cc
-
-    src/events/messages/Audio.cc
-    src/events/messages/Emote.cc
-    src/events/messages/File.cc
-    src/events/messages/Image.cc
-    src/events/messages/Location.cc
-    src/events/messages/Notice.cc
-    src/events/messages/Text.cc
-    src/events/messages/Video.cc
-)
-
 include_directories(include)
 include_directories(include/ui)
-include_directories(include/events)
-include_directories(include/events/messages)
 
 include_directories(libs/lmdbxx)
 include_directories(${LMDB_INCLUDE_DIR})
@@ -324,35 +296,9 @@ file(APPEND ${_qrc} "</qresource> </RCC>")
 qt5_add_resources(LANG_QRC ${_qrc})
 qt5_add_resources(QRC resources/res.qrc)
 
-#
-# Matrix events library.
-#
-add_library(matrix_events ${MATRIX_EVENTS} src/Deserializable.cc)
-target_link_libraries(matrix_events Qt5::Core)
-
 add_subdirectory(libs/matrix-structs)
 
-if (BUILD_TESTS)
-    enable_testing()
-
-    find_package(GTest REQUIRED)
-    include_directories(${GTEST_INCLUDE_DIRS})
-
-    add_executable(events_test tests/events.cc)
-    target_link_libraries(events_test matrix_events ${GTEST_BOTH_LIBRARIES})
-
-    add_executable(event_collection_test tests/event_collection.cc)
-    target_link_libraries(event_collection_test matrix_events ${GTEST_BOTH_LIBRARIES})
-
-    add_executable(message_events tests/message_events.cc)
-    target_link_libraries(message_events matrix_events ${GTEST_BOTH_LIBRARIES})
-
-    add_test(MatrixEvents events_test)
-    add_test(MatrixEventCollection event_collection_test)
-    add_test(MatrixMessageEvents message_events)
-endif()
-
-set(COMMON_LIBS matrix_events matrix_structs Qt5::Widgets Qt5::Network Qt5::Concurrent)
+set(COMMON_LIBS matrix_structs Qt5::Widgets Qt5::Network Qt5::Concurrent)
 
 if(APPVEYOR_BUILD)
     set(NHEKO_LIBS ${COMMON_LIBS} lmdb)
diff --git a/Makefile b/Makefile
index 1a23d6b2..fbbbed9b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,17 +1,12 @@
 
 debug:
-	@cmake -DBUILD_TESTS=OFF -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Debug
+	@cmake -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Debug
 	@cmake --build build
 
 release-debug:
-	@cmake -DBUILD_TESTS=OFF -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo
+	@cmake -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo
 	@cmake --build build
 
-test:
-	@cmake -DBUILD_TESTS=ON -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo
-	@cmake --build build
-	@cd build && GTEST_COLOR=1 ctest --verbose
-
 linux-appimage:
 	@./.ci/linux/deploy.sh
 
diff --git a/include/ChatPage.h b/include/ChatPage.h
index 01f6c5d7..94c54f0b 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -24,9 +24,7 @@
 #include <QTimer>
 #include <QWidget>
 
-#include "MemberEventContent.h"
-#include "MessageEvent.h"
-#include "StateEvent.h"
+#include <mtx.hpp>
 
 class Cache;
 class MatrixClient;
@@ -37,14 +35,11 @@ class RoomSettings;
 class RoomState;
 class SideBarActions;
 class Splitter;
-class SyncResponse;
 class TextInputWidget;
 class TimelineViewManager;
 class TopRoomBar;
 class TypingDisplay;
 class UserInfoWidget;
-class JoinedRoom;
-class LeftRoom;
 
 constexpr int CONSENSUS_TIMEOUT      = 1000;
 constexpr int SHOW_CONTENT_TIMEOUT   = 3000;
@@ -76,8 +71,8 @@ private slots:
         void updateTopBarAvatar(const QString &roomid, const QPixmap &img);
         void updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name);
         void setOwnAvatar(const QPixmap &img);
-        void initialSyncCompleted(const SyncResponse &response);
-        void syncCompleted(const SyncResponse &response);
+        void initialSyncCompleted(const mtx::responses::Sync &response);
+        void syncCompleted(const mtx::responses::Sync &response);
         void syncFailed(const QString &msg);
         void changeTopRoomInfo(const QString &room_id);
         void logout();
@@ -87,26 +82,34 @@ private slots:
 private:
         using UserID      = QString;
         using RoomStates  = QMap<UserID, RoomState>;
-        using JoinedRooms = QMap<UserID, JoinedRoom>;
-        using LeftRooms   = QMap<UserID, LeftRoom>;
-        using Membership  = matrix::events::StateEvent<matrix::events::MemberEventContent>;
-        using Memberships = QMap<UserID, Membership>;
+        using Membership  = mtx::events::StateEvent<mtx::events::state::Member>;
+        using Memberships = std::map<std::string, Membership>;
+
+        using JoinedRooms = std::map<std::string, mtx::responses::JoinedRoom>;
+        using LeftRooms   = std::map<std::string, mtx::responses::LeftRoom>;
 
         void removeLeftRooms(const LeftRooms &rooms);
         void updateJoinedRooms(const JoinedRooms &rooms);
 
-        Memberships getMemberships(const QJsonArray &events) const;
         RoomStates generateMembershipDifference(const JoinedRooms &rooms,
                                                 const RoomStates &states) const;
 
-        void updateTypingUsers(const QString &roomid, const QList<QString> &user_ids);
-        void updateUserMetadata(const QJsonArray &events);
-        void updateUserDisplayName(const Membership &event);
-        void updateUserAvatarUrl(const Membership &event);
+        void updateTypingUsers(const QString &roomid, const std::vector<std::string> &user_ids);
+
+        using MemberEvent = mtx::events::StateEvent<mtx::events::state::Member>;
+        void updateUserDisplayName(const MemberEvent &event);
+        void updateUserAvatarUrl(const MemberEvent &event);
+
         void loadStateFromCache();
         void deleteConfigs();
         void resetUI();
 
+        template<class Collection>
+        Memberships getMemberships(const std::vector<Collection> &events) const;
+
+        template<class Collection>
+        void updateUserMetadata(const std::vector<Collection> &collection);
+
         QHBoxLayout *topLayout_;
         Splitter *splitter;
 
@@ -153,3 +156,37 @@ private:
         // return to the login page.
         int initialSyncFailures = 0;
 };
+
+template<class Collection>
+void
+ChatPage::updateUserMetadata(const std::vector<Collection> &collection)
+{
+        using Member = mtx::events::StateEvent<mtx::events::state::Member>;
+
+        for (auto &event : collection) {
+                if (mpark::holds_alternative<Member>(event)) {
+                        auto member = mpark::get<Member>(event);
+
+                        updateUserAvatarUrl(member);
+                        updateUserDisplayName(member);
+                }
+        }
+}
+
+template<class Collection>
+std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>>
+ChatPage::getMemberships(const std::vector<Collection> &collection) const
+{
+        std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
+
+        using Member = mtx::events::StateEvent<mtx::events::state::Member>;
+
+        for (auto &event : collection) {
+                if (mpark::holds_alternative<Member>(event)) {
+                        auto member = mpark::get<Member>(event);
+                        memberships.emplace(member.state_key, member);
+                }
+        }
+
+        return memberships;
+}
diff --git a/include/MatrixClient.h b/include/MatrixClient.h
index 722a8611..397ba11d 100644
--- a/include/MatrixClient.h
+++ b/include/MatrixClient.h
@@ -20,12 +20,7 @@
 #include <QFileInfo>
 #include <QNetworkAccessManager>
 #include <QUrl>
-
-#include "MessageEvent.h"
-
-class SyncResponse;
-class Profile;
-class RoomMessages;
+#include <mtx.hpp>
 
 /*
  * MatrixClient provides the high level API to communicate with
@@ -40,7 +35,7 @@ public:
         // Client API.
         void initialSync() noexcept;
         void sync() noexcept;
-        void sendRoomMessage(matrix::events::MessageEventType ty,
+        void sendRoomMessage(mtx::events::MessageType ty,
                              int txnId,
                              const QString &roomid,
                              const QString &msg,
@@ -107,15 +102,15 @@ signals:
 
         // Returned profile data for the user's account.
         void getOwnProfileResponse(const QUrl &avatar_url, const QString &display_name);
-        void initialSyncCompleted(const SyncResponse &response);
+        void initialSyncCompleted(const mtx::responses::Sync &response);
         void initialSyncFailed(const QString &msg);
-        void syncCompleted(const SyncResponse &response);
+        void syncCompleted(const mtx::responses::Sync &response);
         void syncFailed(const QString &msg);
         void joinFailed(const QString &msg);
         void messageSent(const QString &event_id, const QString &roomid, const int txn_id);
         void messageSendFailed(const QString &roomid, const int txn_id);
         void emoteSent(const QString &event_id, const QString &roomid, const int txn_id);
-        void messagesRetrieved(const QString &room_id, const RoomMessages &msgs);
+        void messagesRetrieved(const QString &room_id, const mtx::responses::Messages &msgs);
         void joinedRoom(const QString &room_id);
         void leftRoom(const QString &room_id);
 
diff --git a/include/RoomState.h b/include/RoomState.h
index db1cdc68..0e91410c 100644
--- a/include/RoomState.h
+++ b/include/RoomState.h
@@ -21,28 +21,14 @@
 #include <QPixmap>
 #include <QUrl>
 
-#include "AliasesEventContent.h"
-#include "AvatarEventContent.h"
-#include "CanonicalAliasEventContent.h"
-#include "CreateEventContent.h"
-#include "HistoryVisibilityEventContent.h"
-#include "JoinRulesEventContent.h"
-#include "MemberEventContent.h"
-#include "NameEventContent.h"
-#include "PowerLevelsEventContent.h"
-#include "TopicEventContent.h"
-
-#include "Event.h"
-#include "RoomEvent.h"
-#include "StateEvent.h"
-
-namespace events = matrix::events;
+#include <mtx.hpp>
 
 class RoomState
 {
 public:
         RoomState();
-        RoomState(const QJsonArray &events);
+        RoomState(const mtx::responses::Timeline &timeline);
+        RoomState(const mtx::responses::State &state);
 
         // Calculate room data that are not immediatly accessible. Like room name and
         // avatar.
@@ -50,32 +36,37 @@ public:
         // e.g If the room is 1-on-1 name and avatar should be extracted from a user.
         void resolveName();
         void resolveAvatar();
-        void parse(const QJsonObject &object);
+        void parse(const nlohmann::json &object);
 
         QUrl getAvatar() const { return avatar_; };
         QString getName() const { return name_; };
-        QString getTopic() const { return topic.content().topic().simplified(); };
+        QString getTopic() const
+        {
+                return QString::fromStdString(topic.content.topic).simplified();
+        };
 
         void removeLeaveMemberships();
         void update(const RoomState &state);
-        void updateFromEvents(const QJsonArray &events);
 
-        QJsonObject serialize() const;
+        template<class Collection>
+        void updateFromEvents(const std::vector<Collection> &collection);
+
+        std::string serialize() const;
 
         // The latest state events.
-        events::StateEvent<events::AliasesEventContent> aliases;
-        events::StateEvent<events::AvatarEventContent> avatar;
-        events::StateEvent<events::CanonicalAliasEventContent> canonical_alias;
-        events::StateEvent<events::CreateEventContent> create;
-        events::StateEvent<events::HistoryVisibilityEventContent> history_visibility;
-        events::StateEvent<events::JoinRulesEventContent> join_rules;
-        events::StateEvent<events::NameEventContent> name;
-        events::StateEvent<events::PowerLevelsEventContent> power_levels;
-        events::StateEvent<events::TopicEventContent> topic;
+        mtx::events::StateEvent<mtx::events::state::Aliases> aliases;
+        mtx::events::StateEvent<mtx::events::state::Avatar> avatar;
+        mtx::events::StateEvent<mtx::events::state::CanonicalAlias> canonical_alias;
+        mtx::events::StateEvent<mtx::events::state::Create> create;
+        mtx::events::StateEvent<mtx::events::state::HistoryVisibility> history_visibility;
+        mtx::events::StateEvent<mtx::events::state::JoinRules> join_rules;
+        mtx::events::StateEvent<mtx::events::state::Name> name;
+        mtx::events::StateEvent<mtx::events::state::PowerLevels> power_levels;
+        mtx::events::StateEvent<mtx::events::state::Topic> topic;
 
         // Contains the m.room.member events for all the joined users.
-        using UserID = QString;
-        QMap<UserID, events::StateEvent<events::MemberEventContent>> memberships;
+        using UserID = std::string;
+        std::map<UserID, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
 
 private:
         QUrl avatar_;
@@ -85,3 +76,44 @@ private:
         // avatar event this should be empty.
         QString userAvatar_;
 };
+
+template<class Collection>
+void
+RoomState::updateFromEvents(const std::vector<Collection> &collection)
+{
+        using Aliases           = mtx::events::StateEvent<mtx::events::state::Aliases>;
+        using Avatar            = mtx::events::StateEvent<mtx::events::state::Avatar>;
+        using CanonicalAlias    = mtx::events::StateEvent<mtx::events::state::CanonicalAlias>;
+        using Create            = mtx::events::StateEvent<mtx::events::state::Create>;
+        using HistoryVisibility = mtx::events::StateEvent<mtx::events::state::HistoryVisibility>;
+        using JoinRules         = mtx::events::StateEvent<mtx::events::state::JoinRules>;
+        using Member            = mtx::events::StateEvent<mtx::events::state::Member>;
+        using Name              = mtx::events::StateEvent<mtx::events::state::Name>;
+        using PowerLevels       = mtx::events::StateEvent<mtx::events::state::PowerLevels>;
+        using Topic             = mtx::events::StateEvent<mtx::events::state::Topic>;
+
+        for (const auto &event : collection) {
+                if (mpark::holds_alternative<Aliases>(event)) {
+                        this->aliases = mpark::get<Aliases>(event);
+                } else if (mpark::holds_alternative<Avatar>(event)) {
+                        this->avatar = mpark::get<Avatar>(event);
+                } else if (mpark::holds_alternative<CanonicalAlias>(event)) {
+                        this->canonical_alias = mpark::get<CanonicalAlias>(event);
+                } else if (mpark::holds_alternative<Create>(event)) {
+                        this->create = mpark::get<Create>(event);
+                } else if (mpark::holds_alternative<HistoryVisibility>(event)) {
+                        this->history_visibility = mpark::get<HistoryVisibility>(event);
+                } else if (mpark::holds_alternative<JoinRules>(event)) {
+                        this->join_rules = mpark::get<JoinRules>(event);
+                } else if (mpark::holds_alternative<Name>(event)) {
+                        this->name = mpark::get<Name>(event);
+                } else if (mpark::holds_alternative<Member>(event)) {
+                        auto membership = mpark::get<Member>(event);
+                        this->memberships.emplace(membership.state_key, membership);
+                } else if (mpark::holds_alternative<PowerLevels>(event)) {
+                        this->power_levels = mpark::get<PowerLevels>(event);
+                } else if (mpark::holds_alternative<Topic>(event)) {
+                        this->topic = mpark::get<Topic>(event);
+                }
+        }
+}
diff --git a/include/Sync.h b/include/Sync.h
deleted file mode 100644
index d59a57dc..00000000
--- a/include/Sync.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonArray>
-#include <QMap>
-
-#include "Deserializable.h"
-
-class Event : public Deserializable
-{
-public:
-        QJsonObject content() const { return content_; };
-        QJsonObject unsigned_content() const { return unsigned_; };
-
-        QString sender() const { return sender_; };
-        QString state_key() const { return state_key_; };
-        QString type() const { return type_; };
-        QString eventId() const { return event_id_; };
-
-        uint64_t timestamp() const { return origin_server_ts_; };
-
-        void deserialize(const QJsonValue &data) override;
-
-private:
-        QJsonObject content_;
-        QJsonObject unsigned_;
-
-        QString sender_;
-        QString state_key_;
-        QString type_;
-        QString event_id_;
-
-        uint64_t origin_server_ts_;
-};
-
-class State : public Deserializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonArray events() const { return events_; };
-
-private:
-        QJsonArray events_;
-};
-
-class Timeline : public Deserializable
-{
-public:
-        QJsonArray events() const { return events_; };
-        QString previousBatch() const { return prev_batch_; };
-        bool limited() const { return limited_; };
-
-        void deserialize(const QJsonValue &data) override;
-
-private:
-        QJsonArray events_;
-        QString prev_batch_;
-        bool limited_;
-};
-
-// TODO: Add support for account_data, undread_notifications
-class JoinedRoom : public Deserializable
-{
-public:
-        State state() const { return state_; };
-        Timeline timeline() const { return timeline_; };
-        QList<QString> typingUserIDs() const { return typingUserIDs_; };
-
-        void deserialize(const QJsonValue &data) override;
-
-private:
-        State state_;
-        Timeline timeline_;
-        QList<QString> typingUserIDs_;
-        /* AccountData account_data_; */
-        /* UnreadNotifications unread_notifications_; */
-};
-
-class LeftRoom : public Deserializable
-{
-public:
-        State state() const { return state_; };
-        Timeline timeline() const { return timeline_; };
-
-        void deserialize(const QJsonValue &data) override;
-
-private:
-        State state_;
-        Timeline timeline_;
-};
-
-// TODO: Add support for invited and left rooms.
-class Rooms : public Deserializable
-{
-public:
-        QMap<QString, JoinedRoom> join() const { return join_; };
-        QMap<QString, LeftRoom> leave() const { return leave_; };
-        void deserialize(const QJsonValue &data) override;
-
-private:
-        QMap<QString, JoinedRoom> join_;
-        QMap<QString, LeftRoom> leave_;
-};
-
-class SyncResponse : public Deserializable
-{
-public:
-        void deserialize(const QJsonDocument &data) override;
-        QString nextBatch() const { return next_batch_; };
-        Rooms rooms() const { return rooms_; };
-
-private:
-        QString next_batch_;
-        Rooms rooms_;
-};
diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h
index b208d3f4..df309e27 100644
--- a/include/TextInputWidget.h
+++ b/include/TextInputWidget.h
@@ -25,13 +25,10 @@
 #include <QWidget>
 
 #include "FlatButton.h"
-#include "Image.h"
 #include "LoadingIndicator.h"
 
 #include "emoji/PickButton.h"
 
-namespace msgs = matrix::events::messages;
-
 class FilteredTextEdit : public QTextEdit
 {
         Q_OBJECT
diff --git a/include/events/AliasesEventContent.h b/include/events/AliasesEventContent.h
deleted file mode 100644
index 7784fad7..00000000
--- a/include/events/AliasesEventContent.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-#include <QList>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-class AliasesEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QList<QString> aliases() const { return aliases_; };
-
-private:
-        QList<QString> aliases_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/AvatarEventContent.h b/include/events/AvatarEventContent.h
deleted file mode 100644
index 55284aa4..00000000
--- a/include/events/AvatarEventContent.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-#include <QUrl>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-/*
- * A picture that is associated with the room.
- */
-
-class AvatarEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QUrl url() const { return url_; };
-
-private:
-        QUrl url_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/CanonicalAliasEventContent.h b/include/events/CanonicalAliasEventContent.h
deleted file mode 100644
index 6322c001..00000000
--- a/include/events/CanonicalAliasEventContent.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "CanonicalAliasEventContent.h"
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-/*
- * This event is used to inform the room about which alias should be considered
- * the canonical one. This could be for display purposes or as suggestion to
- * users which alias to use to advertise the room.
- */
-
-class CanonicalAliasEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QString alias() const { return alias_; };
-
-private:
-        QString alias_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/CreateEventContent.h b/include/events/CreateEventContent.h
deleted file mode 100644
index 0a47860e..00000000
--- a/include/events/CreateEventContent.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "Deserializable.h"
-
-namespace matrix {
-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
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QString creator() const { return creator_; };
-
-private:
-        // The user_id of the room creator. This is set by the homeserver.
-        QString creator_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/Event.h b/include/events/Event.h
deleted file mode 100644
index f6620a2c..00000000
--- a/include/events/Event.h
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QDebug>
-#include <QJsonValue>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-enum class EventType
-{
-        /// m.room.aliases
-        RoomAliases,
-        /// m.room.avatar
-        RoomAvatar,
-        /// m.room.canonical_alias
-        RoomCanonicalAlias,
-        /// m.room.create
-        RoomCreate,
-        /// m.room.history_visibility
-        RoomHistoryVisibility,
-        /// m.room.join_rules
-        RoomJoinRules,
-        /// m.room.member
-        RoomMember,
-        /// m.room.message
-        RoomMessage,
-        /// m.room.name
-        RoomName,
-        /// m.room.power_levels
-        RoomPowerLevels,
-        /// m.room.topic
-        RoomTopic,
-        // Unsupported event
-        Unsupported,
-};
-
-EventType
-extractEventType(const QJsonObject &data);
-
-bool
-isMessageEvent(EventType type);
-bool
-isStateEvent(EventType type);
-
-class UnsignedData
-  : public Deserializable
-  , public Serializable
-{
-public:
-        double age() const { return age_; }
-        QString transactionId() const { return transaction_id_; }
-
-        bool isEmpty() const { return age_ <= 0 && transaction_id_.isEmpty(); }
-
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-private:
-        double age_ = 0;
-        QString transaction_id_;
-};
-
-template<class Content>
-class Event
-  : public Deserializable
-  , public Serializable
-{
-public:
-        Content content() const;
-        EventType eventType() const;
-        UnsignedData unsignedData() const { return unsignedData_; }
-
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-private:
-        Content content_;
-        EventType type_;
-        UnsignedData unsignedData_;
-};
-
-template<class Content>
-inline Content
-Event<Content>::content() const
-{
-        return content_;
-}
-
-template<class Content>
-inline EventType
-Event<Content>::eventType() const
-{
-        return type_;
-}
-
-template<class Content>
-void
-Event<Content>::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("Event is not a JSON object");
-
-        auto object = data.toObject();
-
-        content_.deserialize(object.value("content"));
-        type_ = extractEventType(object);
-
-        if (object.contains("unsigned"))
-                unsignedData_.deserialize(object.value("unsigned"));
-}
-
-template<class Content>
-QJsonObject
-Event<Content>::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();
-
-        if (!unsignedData_.isEmpty())
-                object["unsigned"] = unsignedData_.serialize();
-
-        return object;
-}
-} // namespace events
-} // namespace matrix
diff --git a/include/events/HistoryVisibilityEventContent.h b/include/events/HistoryVisibilityEventContent.h
deleted file mode 100644
index 1c39ae03..00000000
--- a/include/events/HistoryVisibilityEventContent.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-enum class HistoryVisibility
-{
-        Invited,
-        Joined,
-        Shared,
-        WorldReadable,
-};
-
-class HistoryVisibilityEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        HistoryVisibility historyVisibility() const { return history_visibility_; };
-
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-private:
-        HistoryVisibility history_visibility_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/JoinRulesEventContent.h b/include/events/JoinRulesEventContent.h
deleted file mode 100644
index 4ed9e65f..00000000
--- a/include/events/JoinRulesEventContent.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-enum class JoinRule
-{
-        // A user who wishes to join the room must first receive
-        // an invite to the room from someone already inside of the room.
-        Invite,
-
-        // Reserved but not yet implemented by the Matrix specification.
-        Knock,
-
-        // Reserved but not yet implemented by the Matrix specification.
-        Private,
-
-        /// Anyone can join the room without any prior action.
-        Public,
-};
-
-/*
- * Describes how users are allowed to join the room.
- */
-
-class JoinRulesEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        JoinRule joinRule() const { return join_rule_; };
-
-private:
-        JoinRule join_rule_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/MemberEventContent.h b/include/events/MemberEventContent.h
deleted file mode 100644
index 8b7b1576..00000000
--- a/include/events/MemberEventContent.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-#include <QUrl>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-enum class Membership
-{
-        // The user is banned.
-        Ban,
-
-        // The user has been invited.
-        Invite,
-
-        // The user has joined.
-        Join,
-
-        // The user has requested to join.
-        Knock,
-
-        // The user has left.
-        Leave,
-};
-
-/*
- * The current membership state of a user in the room.
- */
-
-class MemberEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QUrl avatarUrl() const { return avatar_url_; };
-        QString displayName() const { return display_name_; };
-        Membership membershipState() const { return membership_state_; };
-
-private:
-        QUrl avatar_url_;
-        QString display_name_;
-        Membership membership_state_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/MessageEvent.h b/include/events/MessageEvent.h
deleted file mode 100644
index 08cd926f..00000000
--- a/include/events/MessageEvent.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "MessageEventContent.h"
-#include "RoomEvent.h"
-
-namespace matrix {
-namespace events {
-template<class MsgContent>
-class MessageEvent : public RoomEvent<MessageEventContent>
-{
-public:
-        MsgContent msgContent() const;
-
-        void deserialize(const QJsonValue &data) override;
-
-private:
-        MsgContent msg_content_;
-};
-
-template<class MsgContent>
-inline MsgContent
-MessageEvent<MsgContent>::msgContent() const
-{
-        return msg_content_;
-}
-
-template<class MsgContent>
-void
-MessageEvent<MsgContent>::deserialize(const QJsonValue &data)
-{
-        RoomEvent<MessageEventContent>::deserialize(data);
-
-        msg_content_.deserialize(data.toObject().value("content").toObject());
-}
-
-namespace messages {
-struct ThumbnailInfo
-{
-        int h;
-        int w;
-        int size = 0;
-
-        QString mimetype;
-};
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/MessageEventContent.h b/include/events/MessageEventContent.h
deleted file mode 100644
index aa08c066..00000000
--- a/include/events/MessageEventContent.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-enum class MessageEventType
-{
-        // m.audio
-        Audio,
-
-        // m.emote
-        Emote,
-
-        // m.file
-        File,
-
-        // m.image
-        Image,
-
-        // m.location
-        Location,
-
-        // m.notice
-        Notice,
-
-        // m.text
-        Text,
-
-        // m.video
-        Video,
-
-        // Unrecognized message type
-        Unknown,
-};
-
-MessageEventType
-extractMessageEventType(const QJsonObject &data);
-
-class MessageEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QString body() const { return body_; };
-
-private:
-        QString body_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/NameEventContent.h b/include/events/NameEventContent.h
deleted file mode 100644
index 378f689d..00000000
--- a/include/events/NameEventContent.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-/*
- * A human-friendly room name designed to be displayed to the end-user.
- */
-
-class NameEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QString name() const { return name_; };
-
-private:
-        QString name_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/PowerLevelsEventContent.h b/include/events/PowerLevelsEventContent.h
deleted file mode 100644
index 63998871..00000000
--- a/include/events/PowerLevelsEventContent.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-#include <QMap>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-enum class PowerLevels
-{
-        User      = 0,
-        Moderator = 50,
-        Admin     = 100,
-};
-
-/*
- * Defines the power levels (privileges) of users in the room.
- */
-
-class PowerLevelsEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        int banLevel() const { return ban_; };
-        int inviteLevel() const { return invite_; };
-        int kickLevel() const { return kick_; };
-        int redactLevel() const { return redact_; };
-
-        int eventsDefaultLevel() const { return events_default_; };
-        int stateDefaultLevel() const { return state_default_; };
-        int usersDefaultLevel() const { return users_default_; };
-
-        int eventLevel(QString event_type) const;
-        int userLevel(QString user_id) const;
-
-private:
-        int ban_    = static_cast<int>(PowerLevels::Moderator);
-        int invite_ = static_cast<int>(PowerLevels::Moderator);
-        int kick_   = static_cast<int>(PowerLevels::Moderator);
-        int redact_ = static_cast<int>(PowerLevels::Moderator);
-
-        int events_default_ = static_cast<int>(PowerLevels::User);
-        int state_default_  = static_cast<int>(PowerLevels::Moderator);
-        int users_default_  = static_cast<int>(PowerLevels::User);
-
-        QMap<QString, int> events_;
-        QMap<QString, int> users_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/RoomEvent.h b/include/events/RoomEvent.h
deleted file mode 100644
index d80951c7..00000000
--- a/include/events/RoomEvent.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-#include <QString>
-
-#include "Event.h"
-
-namespace matrix {
-namespace events {
-template<class Content>
-class RoomEvent : public Event<Content>
-{
-public:
-        QString eventId() const;
-        QString roomId() const;
-        QString sender() const;
-        uint64_t timestamp() const;
-
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-private:
-        QString event_id_;
-        QString room_id_;
-        QString sender_;
-
-        uint64_t origin_server_ts_;
-};
-
-template<class Content>
-inline QString
-RoomEvent<Content>::eventId() const
-{
-        return event_id_;
-}
-
-template<class Content>
-inline QString
-RoomEvent<Content>::roomId() const
-{
-        return room_id_;
-}
-
-template<class Content>
-inline QString
-RoomEvent<Content>::sender() const
-{
-        return sender_;
-}
-
-template<class Content>
-inline uint64_t
-RoomEvent<Content>::timestamp() const
-{
-        return origin_server_ts_;
-}
-
-template<class Content>
-void
-RoomEvent<Content>::deserialize(const QJsonValue &data)
-{
-        Event<Content>::deserialize(data);
-
-        auto object = data.toObject();
-
-        if (!object.contains("event_id"))
-                throw DeserializationException("event_id key is missing");
-
-        if (!object.contains("origin_server_ts"))
-                throw DeserializationException("origin_server_ts key is missing");
-
-        // FIXME: Synapse doesn't include room id?!
-        /* if (!object.contains("room_id")) */
-        /* 	throw DeserializationException("room_id key is missing"); */
-
-        if (!object.contains("sender"))
-                throw DeserializationException("sender key is missing");
-
-        event_id_         = object.value("event_id").toString();
-        room_id_          = object.value("room_id").toString();
-        sender_           = object.value("sender").toString();
-        origin_server_ts_ = object.value("origin_server_ts").toDouble();
-}
-
-template<class Content>
-QJsonObject
-RoomEvent<Content>::serialize() const
-{
-        QJsonObject object = Event<Content>::serialize();
-
-        object["event_id"]         = event_id_;
-        object["room_id"]          = room_id_;
-        object["sender"]           = sender_;
-        object["origin_server_ts"] = QJsonValue(static_cast<qint64>(origin_server_ts_));
-
-        return object;
-}
-} // namespace events
-} // namespace matrix
diff --git a/include/events/StateEvent.h b/include/events/StateEvent.h
deleted file mode 100644
index 19342a48..00000000
--- a/include/events/StateEvent.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "RoomEvent.h"
-
-namespace matrix {
-namespace events {
-template<class Content>
-class StateEvent : public RoomEvent<Content>
-{
-public:
-        QString stateKey() const;
-        Content previousContent() const;
-
-        void deserialize(const QJsonValue &data);
-        QJsonObject serialize() const;
-
-private:
-        QString state_key_;
-        Content prev_content_;
-};
-
-template<class Content>
-inline QString
-StateEvent<Content>::stateKey() const
-{
-        return state_key_;
-}
-
-template<class Content>
-inline Content
-StateEvent<Content>::previousContent() const
-{
-        return prev_content_;
-}
-
-template<class Content>
-void
-StateEvent<Content>::deserialize(const QJsonValue &data)
-{
-        RoomEvent<Content>::deserialize(data);
-
-        auto object = data.toObject();
-
-        if (!object.contains("state_key"))
-                throw DeserializationException("state_key key is missing");
-
-        state_key_ = object.value("state_key").toString();
-
-        if (object.contains("prev_content"))
-                prev_content_.deserialize(object.value("prev_content"));
-}
-
-template<class Content>
-QJsonObject
-StateEvent<Content>::serialize() const
-{
-        QJsonObject object = RoomEvent<Content>::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
deleted file mode 100644
index 67e21208..00000000
--- a/include/events/TopicEventContent.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonValue>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-/*
- * A topic is a short message detailing what is currently being discussed in the
- * room.
- */
-
-class TopicEventContent
-  : public Deserializable
-  , public Serializable
-{
-public:
-        void deserialize(const QJsonValue &data) override;
-        QJsonObject serialize() const override;
-
-        QString topic() const { return topic_; };
-
-private:
-        QString topic_;
-};
-
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/Audio.h b/include/events/messages/Audio.h
deleted file mode 100644
index b5666d90..00000000
--- a/include/events/messages/Audio.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-struct AudioInfo
-{
-        uint64_t duration;
-        int size = 0;
-
-        QString mimetype;
-};
-
-class Audio : public Deserializable
-{
-public:
-        QString url() const { return url_; };
-        AudioInfo info() const { return info_; };
-
-        void deserialize(const QJsonObject &object) override;
-
-private:
-        QString url_;
-        AudioInfo info_;
-};
-
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/Emote.h b/include/events/messages/Emote.h
deleted file mode 100644
index a11b7c8d..00000000
--- a/include/events/messages/Emote.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-class Emote : public Deserializable
-{
-public:
-        void deserialize(const QJsonObject &obj) override;
-};
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/File.h b/include/events/messages/File.h
deleted file mode 100644
index 9064a556..00000000
--- a/include/events/messages/File.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-#include "MessageEvent.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-struct FileInfo
-{
-        int size = 0;
-
-        QString mimetype;
-        QString thumbnail_url;
-        ThumbnailInfo thumbnail_info;
-};
-
-class File : public Deserializable
-{
-public:
-        QString url() const { return url_; };
-        QString filename() const { return filename_; };
-        FileInfo info() const { return info_; };
-
-        void deserialize(const QJsonObject &object) override;
-
-private:
-        QString url_;
-        QString filename_;
-
-        FileInfo info_;
-};
-
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/Image.h b/include/events/messages/Image.h
deleted file mode 100644
index 03c7a368..00000000
--- a/include/events/messages/Image.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-#include "MessageEvent.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-struct ImageInfo
-{
-        int h;
-        int w;
-        int size = 0;
-
-        QString mimetype;
-        QString thumbnail_url;
-        ThumbnailInfo thumbnail_info;
-};
-
-class Image : public Deserializable
-{
-public:
-        QString url() const { return url_; };
-        ImageInfo info() const { return info_; };
-
-        void deserialize(const QJsonObject &object) override;
-
-private:
-        QString url_;
-        ImageInfo info_;
-};
-
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/Location.h b/include/events/messages/Location.h
deleted file mode 100644
index 27722b37..00000000
--- a/include/events/messages/Location.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-#include "MessageEvent.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-struct LocationInfo
-{
-        QString thumbnail_url;
-        ThumbnailInfo thumbnail_info;
-};
-
-class Location : public Deserializable
-{
-public:
-        QString geoUri() const { return geo_uri_; };
-        LocationInfo info() const { return info_; };
-
-        void deserialize(const QJsonObject &object) override;
-
-private:
-        QString geo_uri_;
-
-        LocationInfo info_;
-};
-
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/Notice.h b/include/events/messages/Notice.h
deleted file mode 100644
index 66f4386d..00000000
--- a/include/events/messages/Notice.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-class Notice : public Deserializable
-{
-public:
-        void deserialize(const QJsonObject &obj) override;
-};
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/Text.h b/include/events/messages/Text.h
deleted file mode 100644
index c3182dc5..00000000
--- a/include/events/messages/Text.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-class Text : public Deserializable
-{
-public:
-        void deserialize(const QJsonObject &obj) override;
-};
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/events/messages/Video.h b/include/events/messages/Video.h
deleted file mode 100644
index 6aeaf4d5..00000000
--- a/include/events/messages/Video.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-#include "MessageEvent.h"
-
-namespace matrix {
-namespace events {
-namespace messages {
-struct VideoInfo
-{
-        int h;
-        int w;
-        int size = 0;
-        int duration;
-
-        QString mimetype;
-        QString thumbnail_url;
-        ThumbnailInfo thumbnail_info;
-};
-
-class Video : public Deserializable
-{
-public:
-        QString url() const { return url_; };
-        VideoInfo info() const { return info_; };
-
-        void deserialize(const QJsonObject &object) override;
-
-private:
-        QString url_;
-        VideoInfo info_;
-};
-
-} // namespace messages
-} // namespace events
-} // namespace matrix
diff --git a/include/timeline/TimelineItem.h b/include/timeline/TimelineItem.h
index e04cbeae..17b110fc 100644
--- a/include/timeline/TimelineItem.h
+++ b/include/timeline/TimelineItem.h
@@ -25,16 +25,7 @@
 #include <QStyle>
 #include <QStyleOption>
 
-#include "Audio.h"
-#include "Emote.h"
-#include "File.h"
-#include "Image.h"
-#include "Notice.h"
-#include "Text.h"
-#include "Video.h"
-
 #include "AvatarProvider.h"
-#include "MessageEvent.h"
 #include "RoomInfoListItem.h"
 #include "TimelineViewManager.h"
 
@@ -44,26 +35,23 @@ class VideoItem;
 class FileItem;
 class Avatar;
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
-
 class TimelineItem : public QWidget
 {
         Q_OBJECT
 public:
-        TimelineItem(const events::MessageEvent<msgs::Notice> &e,
+        TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e,
                      bool with_sender,
                      QWidget *parent = 0);
-        TimelineItem(const events::MessageEvent<msgs::Text> &e,
+        TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &e,
                      bool with_sender,
                      QWidget *parent = 0);
-        TimelineItem(const events::MessageEvent<msgs::Emote> &e,
+        TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &e,
                      bool with_sender,
                      QWidget *parent = 0);
 
         // For local messages.
         // m.text & m.emote
-        TimelineItem(events::MessageEventType ty,
+        TimelineItem(mtx::events::MessageType ty,
                      const QString &userid,
                      QString body,
                      bool withSender,
@@ -75,19 +63,19 @@ public:
         TimelineItem(VideoItem *item, const QString &userid, bool withSender, QWidget *parent = 0);
 
         TimelineItem(ImageItem *img,
-                     const events::MessageEvent<msgs::Image> &e,
+                     const mtx::events::RoomEvent<mtx::events::msg::Image> &e,
                      bool with_sender,
                      QWidget *parent);
         TimelineItem(FileItem *file,
-                     const events::MessageEvent<msgs::File> &e,
+                     const mtx::events::RoomEvent<mtx::events::msg::File> &e,
                      bool with_sender,
                      QWidget *parent);
         TimelineItem(AudioItem *audio,
-                     const events::MessageEvent<msgs::Audio> &e,
+                     const mtx::events::RoomEvent<mtx::events::msg::Audio> &e,
                      bool with_sender,
                      QWidget *parent);
         TimelineItem(VideoItem *video,
-                     const events::MessageEvent<msgs::Video> &e,
+                     const mtx::events::RoomEvent<mtx::events::msg::Video> &e,
                      bool with_sender,
                      QWidget *parent);
 
@@ -185,16 +173,17 @@ TimelineItem::setupWidgetLayout(Widget *widget,
 {
         init();
 
-        event_id_ = event.eventId();
+        event_id_         = QString::fromStdString(event.event_id);
+        const auto sender = QString::fromStdString(event.sender);
 
-        auto timestamp   = QDateTime::fromMSecsSinceEpoch(event.timestamp());
-        auto displayName = TimelineViewManager::displayName(event.sender());
+        auto timestamp   = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
+        auto displayName = TimelineViewManager::displayName(sender);
 
         QSettings settings;
-        descriptionMsg_ = {event.sender() == settings.value("auth/user_id") ? "You" : displayName,
-                           event.sender(),
+        descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
+                           sender,
                            msgDescription,
-                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
+                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.origin_server_ts))};
 
         generateTimestamp(timestamp);
 
@@ -209,7 +198,7 @@ TimelineItem::setupWidgetLayout(Widget *widget,
 
                 mainLayout_->addLayout(headerLayout_);
 
-                AvatarProvider::resolve(event.sender(), this);
+                AvatarProvider::resolve(sender, this);
         } else {
                 setupSimpleLayout();
         }
diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h
index 5262d20d..52bf0165 100644
--- a/include/timeline/TimelineView.h
+++ b/include/timeline/TimelineView.h
@@ -27,39 +27,27 @@
 #include <QStyle>
 #include <QStyleOption>
 
-#include "Audio.h"
-#include "Emote.h"
-#include "File.h"
-#include "Image.h"
-#include "Notice.h"
-#include "Text.h"
-#include "Video.h"
+#include <mtx.hpp>
 
 #include "MatrixClient.h"
-#include "MessageEvent.h"
 #include "TimelineItem.h"
 
 class FloatingButton;
-class RoomMessages;
 class ScrollBar;
-class Timeline;
 struct DescInfo;
 
-namespace msgs   = matrix::events::messages;
-namespace events = matrix::events;
-
 // Contains info about a message shown in the history view
 // but not yet confirmed by the homeserver through sync.
 struct PendingMessage
 {
-        matrix::events::MessageEventType ty;
+        mtx::events::MessageType ty;
         int txn_id;
         QString body;
         QString filename;
         QString event_id;
         TimelineItem *widget;
 
-        PendingMessage(matrix::events::MessageEventType ty,
+        PendingMessage(mtx::events::MessageType ty,
                        int txn_id,
                        QString body,
                        QString filename,
@@ -86,7 +74,7 @@ class TimelineView : public QWidget
         Q_OBJECT
 
 public:
-        TimelineView(const Timeline &timeline,
+        TimelineView(const mtx::responses::Timeline &timeline,
                      QSharedPointer<MatrixClient> client,
                      const QString &room_id,
                      QWidget *parent = 0);
@@ -95,10 +83,10 @@ public:
                      QWidget *parent = 0);
 
         // Add new events at the end of the timeline.
-        int addEvents(const Timeline &timeline);
-        void addUserMessage(matrix::events::MessageEventType ty, const QString &msg);
+        int addEvents(const mtx::responses::Timeline &timeline);
+        void addUserMessage(mtx::events::MessageType ty, const QString &msg);
 
-        template<class Widget, events::MessageEventType MsgType>
+        template<class Widget, mtx::events::MessageType MsgType>
         void addUserMessage(const QString &url, const QString &filename);
         void updatePendingMessage(int txn_id, QString event_id);
         void scrollDown();
@@ -109,7 +97,7 @@ public slots:
         void fetchHistory();
 
         // Add old events at the top of the timeline.
-        void addBackwardsEvents(const QString &room_id, const RoomMessages &msgs);
+        void addBackwardsEvents(const QString &room_id, const mtx::responses::Messages &msgs);
 
         // Whether or not the initial batch has been loaded.
         bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; }
@@ -135,13 +123,14 @@ private:
         void notifyForLastEvent();
         void readLastEvent() const;
         QString getLastEventId() const;
+        QString getEventSender(const mtx::events::collections::TimelineEvents &event) const;
 
         template<class Event, class Widget>
-        TimelineItem *processMessageEvent(const QJsonObject &event, TimelineDirection direction);
+        TimelineItem *processMessageEvent(const Event &event, TimelineDirection direction);
 
         // TODO: Remove this eventually.
         template<class Event>
-        TimelineItem *processMessageEvent(const QJsonObject &event, TimelineDirection direction);
+        TimelineItem *processMessageEvent(const Event &event, TimelineDirection direction);
 
         // For events with custom display widgets.
         template<class Event, class Widget>
@@ -164,7 +153,8 @@ private:
         void handleNewUserMessage(PendingMessage msg);
 
         // Return nullptr if the event couldn't be parsed.
-        TimelineItem *parseMessageEvent(const QJsonObject &event, TimelineDirection direction);
+        TimelineItem *parseMessageEvent(const mtx::events::collections::TimelineEvents &event,
+                                        TimelineDirection direction);
 
         QVBoxLayout *top_layout_;
         QVBoxLayout *scroll_layout_;
@@ -207,7 +197,7 @@ private:
         QSharedPointer<MatrixClient> client_;
 };
 
-template<class Widget, events::MessageEventType MsgType>
+template<class Widget, mtx::events::MessageType MsgType>
 void
 TimelineView::addUserMessage(const QString &url, const QString &filename)
 {
@@ -252,62 +242,50 @@ TimelineView::createTimelineItem(const Event &event, bool withSender)
 
 template<class Event>
 TimelineItem *
-TimelineView::processMessageEvent(const QJsonObject &data, TimelineDirection direction)
+TimelineView::processMessageEvent(const Event &event, TimelineDirection direction)
 {
-        Event event;
-
-        try {
-                event.deserialize(data);
-        } catch (const DeserializationException &e) {
-                qWarning() << e.what() << data;
-                return nullptr;
-        }
+        const auto event_id = QString::fromStdString(event.event_id);
+        const auto sender   = QString::fromStdString(event.sender);
 
-        if (isDuplicate(event.eventId()))
+        if (isDuplicate(event_id))
                 return nullptr;
 
-        eventIds_[event.eventId()] = true;
+        eventIds_[event_id] = true;
 
-        QString txnid = event.unsignedData().transactionId();
-        if (!txnid.isEmpty() && isPendingMessage(txnid, event.sender(), local_user_)) {
+        const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
+        if (!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) {
                 removePendingMessage(txnid);
                 return nullptr;
         }
 
-        auto with_sender = isSenderRendered(event.sender(), direction);
+        auto with_sender = isSenderRendered(sender, direction);
 
-        updateLastSender(event.sender(), direction);
+        updateLastSender(sender, direction);
 
         return createTimelineItem<Event>(event, with_sender);
 }
 
 template<class Event, class Widget>
 TimelineItem *
-TimelineView::processMessageEvent(const QJsonObject &data, TimelineDirection direction)
+TimelineView::processMessageEvent(const Event &event, TimelineDirection direction)
 {
-        Event event;
-
-        try {
-                event.deserialize(data);
-        } catch (const DeserializationException &e) {
-                qWarning() << e.what() << data;
-                return nullptr;
-        }
+        const auto event_id = QString::fromStdString(event.event_id);
+        const auto sender   = QString::fromStdString(event.sender);
 
-        if (isDuplicate(event.eventId()))
+        if (isDuplicate(event_id))
                 return nullptr;
 
-        eventIds_[event.eventId()] = true;
+        eventIds_[event_id] = true;
 
-        QString txnid = event.unsignedData().transactionId();
-        if (!txnid.isEmpty() && isPendingMessage(txnid, event.sender(), local_user_)) {
+        const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
+        if (!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) {
                 removePendingMessage(txnid);
                 return nullptr;
         }
 
-        auto with_sender = isSenderRendered(event.sender(), direction);
+        auto with_sender = isSenderRendered(sender, direction);
 
-        updateLastSender(event.sender(), direction);
+        updateLastSender(sender, direction);
 
         return createTimelineItem<Event, Widget>(event, with_sender);
 }
diff --git a/include/timeline/TimelineViewManager.h b/include/timeline/TimelineViewManager.h
index edb44ecd..2c32da16 100644
--- a/include/timeline/TimelineViewManager.h
+++ b/include/timeline/TimelineViewManager.h
@@ -21,12 +21,10 @@
 #include <QSharedPointer>
 #include <QStackedWidget>
 
-#include "MessageEvent.h"
+#include <mtx.hpp>
 
-class JoinedRoom;
 class MatrixClient;
 class RoomInfoListItem;
-class Rooms;
 class TimelineView;
 struct DescInfo;
 
@@ -39,14 +37,14 @@ public:
         ~TimelineViewManager();
 
         // Initialize with timeline events.
-        void initialize(const Rooms &rooms);
+        void initialize(const mtx::responses::Rooms &rooms);
         // Empty initialization.
         void initialize(const QList<QString> &rooms);
 
-        void addRoom(const JoinedRoom &room, const QString &room_id);
+        void addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id);
         void addRoom(const QString &room_id);
 
-        void sync(const Rooms &rooms);
+        void sync(const mtx::responses::Rooms &rooms);
         void clearAll();
 
         // Check if all the timelines have been loaded.
diff --git a/include/timeline/widgets/AudioItem.h b/include/timeline/widgets/AudioItem.h
index 1104996f..f8e7cc07 100644
--- a/include/timeline/widgets/AudioItem.h
+++ b/include/timeline/widgets/AudioItem.h
@@ -24,12 +24,9 @@
 #include <QSharedPointer>
 #include <QWidget>
 
-#include "Audio.h"
 #include "MatrixClient.h"
-#include "MessageEvent.h"
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
+#include <mtx.hpp>
 
 class AudioItem : public QWidget
 {
@@ -46,7 +43,7 @@ class AudioItem : public QWidget
 
 public:
         AudioItem(QSharedPointer<MatrixClient> client,
-                  const events::MessageEvent<msgs::Audio> &event,
+                  const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
                   QWidget *parent = nullptr);
 
         AudioItem(QSharedPointer<MatrixClient> client,
@@ -94,7 +91,7 @@ private:
         QString readableFileSize_;
         QString filenameToSave_;
 
-        events::MessageEvent<msgs::Audio> event_;
+        mtx::events::RoomEvent<mtx::events::msg::Audio> event_;
         QSharedPointer<MatrixClient> client_;
 
         QMediaPlayer *player_;
diff --git a/include/timeline/widgets/FileItem.h b/include/timeline/widgets/FileItem.h
index 47e81867..fd0b0249 100644
--- a/include/timeline/widgets/FileItem.h
+++ b/include/timeline/widgets/FileItem.h
@@ -23,12 +23,9 @@
 #include <QSharedPointer>
 #include <QWidget>
 
-#include "File.h"
-#include "MatrixClient.h"
-#include "MessageEvent.h"
+#include <mtx.hpp>
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
+#include "MatrixClient.h"
 
 class FileItem : public QWidget
 {
@@ -40,7 +37,7 @@ class FileItem : public QWidget
 
 public:
         FileItem(QSharedPointer<MatrixClient> client,
-                 const events::MessageEvent<msgs::File> &event,
+                 const mtx::events::RoomEvent<mtx::events::msg::File> &event,
                  QWidget *parent = nullptr);
 
         FileItem(QSharedPointer<MatrixClient> client,
@@ -75,7 +72,7 @@ private:
         QString readableFileSize_;
         QString filenameToSave_;
 
-        events::MessageEvent<msgs::File> event_;
+        mtx::events::RoomEvent<mtx::events::msg::File> event_;
         QSharedPointer<MatrixClient> client_;
 
         QIcon icon_;
diff --git a/include/timeline/widgets/ImageItem.h b/include/timeline/widgets/ImageItem.h
index c4f6998a..931c17dd 100644
--- a/include/timeline/widgets/ImageItem.h
+++ b/include/timeline/widgets/ImageItem.h
@@ -22,19 +22,16 @@
 #include <QSharedPointer>
 #include <QWidget>
 
-#include "Image.h"
-#include "MatrixClient.h"
-#include "MessageEvent.h"
+#include <mtx.hpp>
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
+#include "MatrixClient.h"
 
 class ImageItem : public QWidget
 {
         Q_OBJECT
 public:
         ImageItem(QSharedPointer<MatrixClient> client,
-                  const events::MessageEvent<msgs::Image> &event,
+                  const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
                   QWidget *parent = nullptr);
 
         ImageItem(QSharedPointer<MatrixClient> client,
@@ -72,7 +69,7 @@ private:
 
         int bottom_height_ = 30;
 
-        events::MessageEvent<msgs::Image> event_;
+        mtx::events::RoomEvent<mtx::events::msg::Image> event_;
 
         QSharedPointer<MatrixClient> client_;
 };
diff --git a/include/timeline/widgets/VideoItem.h b/include/timeline/widgets/VideoItem.h
index aa2a5da3..88ff21ec 100644
--- a/include/timeline/widgets/VideoItem.h
+++ b/include/timeline/widgets/VideoItem.h
@@ -23,11 +23,8 @@
 #include <QWidget>
 
 #include "MatrixClient.h"
-#include "MessageEvent.h"
-#include "Video.h"
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
+#include <mtx.hpp>
 
 class VideoItem : public QWidget
 {
@@ -35,7 +32,7 @@ class VideoItem : public QWidget
 
 public:
         VideoItem(QSharedPointer<MatrixClient> client,
-                  const events::MessageEvent<msgs::Video> &event,
+                  const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
                   QWidget *parent = nullptr);
 
         VideoItem(QSharedPointer<MatrixClient> client,
@@ -53,6 +50,6 @@ private:
 
         QLabel *label_;
 
-        events::MessageEvent<msgs::Video> event_;
+        mtx::events::RoomEvent<mtx::events::msg::Video> event_;
         QSharedPointer<MatrixClient> client_;
 };
diff --git a/libs/matrix-structs b/libs/matrix-structs
-Subproject 190f297478153930780a119268b908076599c8d
+Subproject ea10ea843bccebf54b7b4ebdf1be92a3624597b
diff --git a/src/Cache.cc b/src/Cache.cc
index dc2c8a9f..087dd4bc 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -22,11 +22,8 @@
 #include <QStandardPaths>
 
 #include "Cache.h"
-#include "MemberEventContent.h"
 #include "RoomState.h"
 
-namespace events = matrix::events;
-
 static const lmdb::val NEXT_BATCH_KEY("next_batch");
 static const lmdb::val transactionID("transaction_id");
 
@@ -122,13 +119,10 @@ Cache::setState(const QString &nextBatchToken, const QMap<QString, RoomState> &s
 void
 Cache::insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state)
 {
-        auto stateEvents = QJsonDocument(state.serialize()).toBinaryData();
+        auto stateEvents = state.serialize();
         auto id          = roomid.toUtf8();
 
-        lmdb::dbi_put(txn,
-                      roomDb_,
-                      lmdb::val(id.data(), id.size()),
-                      lmdb::val(stateEvents.data(), stateEvents.size()));
+        lmdb::dbi_put(txn, roomDb_, lmdb::val(id.data(), id.size()), lmdb::val(stateEvents));
 
         for (const auto &membership : state.memberships) {
                 lmdb::dbi membersDb =
@@ -136,31 +130,29 @@ Cache::insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &s
 
                 // The user_id this membership event relates to, is used
                 // as the index on the membership database.
-                auto key         = membership.stateKey().toUtf8();
-                auto memberEvent = QJsonDocument(membership.serialize()).toBinaryData();
+                auto key = membership.second.state_key;
+
+                // Serialize membership event.
+                nlohmann::json data     = membership.second;
+                std::string memberEvent = data.dump();
 
-                switch (membership.content().membershipState()) {
+                switch (membership.second.content.membership) {
                 // We add or update (e.g invite -> join) a new user to the membership
                 // list.
-                case events::Membership::Invite:
-                case events::Membership::Join: {
-                        lmdb::dbi_put(txn,
-                                      membersDb,
-                                      lmdb::val(key.data(), key.size()),
-                                      lmdb::val(memberEvent.data(), memberEvent.size()));
+                case mtx::events::state::Membership::Invite:
+                case mtx::events::state::Membership::Join: {
+                        lmdb::dbi_put(txn, membersDb, lmdb::val(key), lmdb::val(memberEvent));
                         break;
                 }
                 // We remove the user from the membership list.
-                case events::Membership::Leave:
-                case events::Membership::Ban: {
-                        lmdb::dbi_del(txn,
-                                      membersDb,
-                                      lmdb::val(key.data(), key.size()),
-                                      lmdb::val(memberEvent.data(), memberEvent.size()));
+                case mtx::events::state::Membership::Leave:
+                case mtx::events::state::Membership::Ban: {
+                        lmdb::dbi_del(txn, membersDb, lmdb::val(key), lmdb::val(memberEvent));
                         break;
                 }
-                case events::Membership::Knock: {
-                        qWarning() << "Skipping knock membership" << roomid << key;
+                case mtx::events::state::Membership::Knock: {
+                        qWarning()
+                          << "Skipping knock membership" << roomid << QString::fromStdString(key);
                         break;
                 }
                 }
@@ -194,14 +186,13 @@ Cache::states()
         // Retrieve all the room names.
         while (cursor.get(room, stateData, MDB_NEXT)) {
                 auto roomid = QString::fromUtf8(room.data(), room.size());
-                auto json =
-                  QJsonDocument::fromBinaryData(QByteArray(stateData.data(), stateData.size()));
+                auto json   = nlohmann::json::parse(stateData);
 
                 RoomState state;
-                state.parse(json.object());
+                state.parse(json);
 
                 auto memberDb = lmdb::dbi::open(txn, roomid.toStdString().c_str(), MDB_CREATE);
-                QMap<QString, events::StateEvent<events::MemberEventContent>> members;
+                std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> members;
 
                 auto memberCursor = lmdb::cursor::open(txn, memberDb);
 
@@ -209,17 +200,15 @@ Cache::states()
                 std::string memberContent;
 
                 while (memberCursor.get(memberId, memberContent, MDB_NEXT)) {
-                        auto userid = QString::fromUtf8(memberId.data(), memberId.size());
-                        auto data   = QJsonDocument::fromBinaryData(
-                          QByteArray(memberContent.data(), memberContent.size()));
+                        auto userid = QString::fromStdString(memberId);
 
                         try {
-                                events::StateEvent<events::MemberEventContent> member;
-                                member.deserialize(data.object());
-                                members.insert(userid, member);
-                        } catch (const DeserializationException &e) {
-                                qWarning() << e.what();
-                                qWarning() << "Fault while parsing member event" << data.object();
+                                auto data = nlohmann::json::parse(memberContent);
+                                mtx::events::StateEvent<mtx::events::state::Member> member = data;
+                                members.emplace(memberId, member);
+                        } catch (std::exception &e) {
+                                qWarning() << "Fault while parsing member event" << e.what()
+                                           << QString::fromStdString(memberContent);
                                 continue;
                         }
                 }
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index bbed7359..ab5aa263 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -32,7 +32,6 @@
 #include "RoomState.h"
 #include "SideBarActions.h"
 #include "Splitter.h"
-#include "Sync.h"
 #include "TextInputWidget.h"
 #include "Theme.h"
 #include "TopRoomBar.h"
@@ -44,8 +43,6 @@
 constexpr int MAX_INITIAL_SYNC_FAILURES = 5;
 constexpr int SYNC_RETRY_TIMEOUT        = 10000;
 
-namespace events = matrix::events;
-
 ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
   : QWidget(parent)
   , client_(client)
@@ -219,15 +216,13 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
                         view_manager_->queueAudioMessage(roomid, filename, url);
                 });
 
-        connect(client_.data(),
-                SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)),
-                this,
-                SLOT(updateTopBarAvatar(const QString &, const QPixmap &)));
+        connect(
+          client_.data(), &MatrixClient::roomAvatarRetrieved, this, &ChatPage::updateTopBarAvatar);
 
         connect(client_.data(),
-                SIGNAL(initialSyncCompleted(const SyncResponse &)),
+                &MatrixClient::initialSyncCompleted,
                 this,
-                SLOT(initialSyncCompleted(const SyncResponse &)));
+                &ChatPage::initialSyncCompleted);
         connect(client_.data(), &MatrixClient::initialSyncFailed, this, [=](const QString &msg) {
                 if (client_->getHomeServer().isEmpty()) {
                         deleteConfigs();
@@ -251,29 +246,17 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
 
                 client_->initialSync();
         });
-        connect(client_.data(),
-                SIGNAL(syncCompleted(const SyncResponse &)),
-                this,
-                SLOT(syncCompleted(const SyncResponse &)));
-        connect(client_.data(),
-                SIGNAL(syncFailed(const QString &)),
-                this,
-                SLOT(syncFailed(const QString &)));
+        connect(client_.data(), &MatrixClient::syncCompleted, this, &ChatPage::syncCompleted);
+        connect(client_.data(), &MatrixClient::syncFailed, this, &ChatPage::syncFailed);
         connect(client_.data(),
                 &MatrixClient::getOwnProfileResponse,
                 this,
                 &ChatPage::updateOwnProfileInfo);
-        connect(client_.data(),
-                SIGNAL(ownAvatarRetrieved(const QPixmap &)),
-                this,
-                SLOT(setOwnAvatar(const QPixmap &)));
+        connect(client_.data(), &MatrixClient::ownAvatarRetrieved, this, &ChatPage::setOwnAvatar);
         connect(client_.data(), &MatrixClient::joinedRoom, this, [=]() {
                 emit showNotification("You joined the room.");
         });
-        connect(client_.data(),
-                SIGNAL(leftRoom(const QString &)),
-                this,
-                SLOT(removeRoom(const QString &)));
+        connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
 
         showContentTimer_ = new QTimer(this);
         showContentTimer_->setSingleShot(true);
@@ -383,32 +366,34 @@ ChatPage::syncFailed(const QString &msg)
 }
 
 void
-ChatPage::syncCompleted(const SyncResponse &response)
+ChatPage::syncCompleted(const mtx::responses::Sync &response)
 {
-        updateJoinedRooms(response.rooms().join());
-        removeLeftRooms(response.rooms().leave());
+        updateJoinedRooms(response.rooms.join);
+        removeLeftRooms(response.rooms.leave);
 
-        auto stateDiff = generateMembershipDifference(response.rooms().join(), state_manager_);
-        QtConcurrent::run(cache_.data(), &Cache::setState, response.nextBatch(), stateDiff);
+        const auto nextBatchToken = QString::fromStdString(response.next_batch);
+
+        auto stateDiff = generateMembershipDifference(response.rooms.join, state_manager_);
+        QtConcurrent::run(cache_.data(), &Cache::setState, nextBatchToken, stateDiff);
 
         room_list_->sync(state_manager_, settingsManager_);
-        view_manager_->sync(response.rooms());
+        view_manager_->sync(response.rooms);
 
-        client_->setNextBatchToken(response.nextBatch());
+        client_->setNextBatchToken(nextBatchToken);
         client_->sync();
 }
 
 void
-ChatPage::initialSyncCompleted(const SyncResponse &response)
+ChatPage::initialSyncCompleted(const mtx::responses::Sync &response)
 {
-        auto joined = response.rooms().join();
+        auto joined = response.rooms.join;
 
-        for (auto it = joined.constBegin(); it != joined.constEnd(); ++it) {
+        for (auto it = joined.cbegin(); it != joined.cend(); ++it) {
                 RoomState room_state;
 
                 // Build the current state from the timeline and state events.
-                room_state.updateFromEvents(it.value().state().events());
-                room_state.updateFromEvents(it.value().timeline().events());
+                room_state.updateFromEvents(it->second.state.events);
+                room_state.updateFromEvents(it->second.timeline.events);
 
                 // Remove redundant memberships.
                 room_state.removeLeaveMemberships();
@@ -417,27 +402,32 @@ ChatPage::initialSyncCompleted(const SyncResponse &response)
                 room_state.resolveName();
                 room_state.resolveAvatar();
 
-                state_manager_.insert(it.key(), room_state);
-                settingsManager_.insert(it.key(),
-                                        QSharedPointer<RoomSettings>(new RoomSettings(it.key())));
+                const auto room_id = QString::fromStdString(it->first);
+
+                state_manager_.insert(room_id, room_state);
+                settingsManager_.insert(room_id,
+                                        QSharedPointer<RoomSettings>(new RoomSettings(room_id)));
 
                 for (const auto membership : room_state.memberships) {
-                        updateUserDisplayName(membership);
-                        updateUserAvatarUrl(membership);
+                        updateUserDisplayName(membership.second);
+                        updateUserAvatarUrl(membership.second);
                 }
 
                 QApplication::processEvents();
         }
 
-        QtConcurrent::run(cache_.data(), &Cache::setState, response.nextBatch(), state_manager_);
+        QtConcurrent::run(cache_.data(),
+                          &Cache::setState,
+                          QString::fromStdString(response.next_batch),
+                          state_manager_);
 
         // Populate timelines with messages.
-        view_manager_->initialize(response.rooms());
+        view_manager_->initialize(response.rooms);
 
         // Initialize room list.
         room_list_->setInitialRooms(settingsManager_, state_manager_);
 
-        client_->setNextBatchToken(response.nextBatch());
+        client_->setNextBatchToken(QString::fromStdString(response.next_batch));
         client_->sync();
 
         emit contentLoaded();
@@ -527,8 +517,8 @@ ChatPage::loadStateFromCache()
 
                 // Resolve user avatars.
                 for (const auto membership : room_state.memberships) {
-                        updateUserDisplayName(membership);
-                        updateUserAvatarUrl(membership);
+                        updateUserDisplayName(membership.second);
+                        updateUserAvatarUrl(membership.second);
                 }
         }
 
@@ -579,7 +569,8 @@ ChatPage::showQuickSwitcher()
         QMap<QString, QString> rooms;
 
         for (auto it = state_manager_.constBegin(); it != state_manager_.constEnd(); ++it) {
-                QString deambiguator = it.value().canonical_alias.content().alias();
+                QString deambiguator =
+                  QString::fromStdString(it.value().canonical_alias.content.alias);
                 if (deambiguator == "")
                         deambiguator = it.key();
                 rooms.insert(it.value().getName() + " (" + deambiguator + ")", it.key());
@@ -623,7 +614,7 @@ ChatPage::removeRoom(const QString &room_id)
 }
 
 void
-ChatPage::updateTypingUsers(const QString &roomid, const QList<QString> &user_ids)
+ChatPage::updateTypingUsers(const QString &roomid, const std::vector<std::string> &user_ids)
 {
         QStringList users;
 
@@ -631,9 +622,12 @@ ChatPage::updateTypingUsers(const QString &roomid, const QList<QString> &user_id
         QString user_id = settings.value("auth/user_id").toString();
 
         for (const auto uid : user_ids) {
-                if (uid == user_id)
+                auto user = QString::fromStdString(uid);
+
+                if (user == user_id)
                         continue;
-                users.append(TimelineViewManager::displayName(uid));
+
+                users.append(TimelineViewManager::displayName(user));
         }
 
         users.sort();
@@ -646,186 +640,118 @@ ChatPage::updateTypingUsers(const QString &roomid, const QList<QString> &user_id
 }
 
 void
-ChatPage::updateUserMetadata(const QJsonArray &events)
-{
-        events::EventType ty;
-
-        for (const auto &event : events) {
-                try {
-                        ty = events::extractEventType(event.toObject());
-                } catch (const DeserializationException &e) {
-                        qWarning() << e.what() << event;
-                        continue;
-                }
-
-                if (!events::isStateEvent(ty))
-                        continue;
-
-                try {
-                        switch (ty) {
-                        case events::EventType::RoomMember: {
-                                events::StateEvent<events::MemberEventContent> member;
-                                member.deserialize(event);
-
-                                updateUserAvatarUrl(member);
-                                updateUserDisplayName(member);
-
-                                break;
-                        }
-                        default: {
-                                continue;
-                        }
-                        }
-                } catch (const DeserializationException &e) {
-                        qWarning() << e.what() << event;
-                        continue;
-                }
-        }
-}
-
-void
-ChatPage::updateUserAvatarUrl(const events::StateEvent<events::MemberEventContent> &membership)
+ChatPage::updateUserAvatarUrl(const mtx::events::StateEvent<mtx::events::state::Member> &membership)
 {
-        auto uid = membership.sender();
-        auto url = membership.content().avatarUrl();
+        auto uid = QString::fromStdString(membership.sender);
+        auto url = QString::fromStdString(membership.content.avatar_url);
 
-        if (!url.toString().isEmpty())
+        if (!url.isEmpty())
                 AvatarProvider::setAvatarUrl(uid, url);
 }
 
 void
-ChatPage::updateUserDisplayName(const events::StateEvent<events::MemberEventContent> &membership)
+ChatPage::updateUserDisplayName(
+  const mtx::events::StateEvent<mtx::events::state::Member> &membership)
 {
-        auto displayName = membership.content().displayName();
+        auto displayName = QString::fromStdString(membership.content.display_name);
+        auto stateKey    = QString::fromStdString(membership.state_key);
 
         if (!displayName.isEmpty())
-                TimelineViewManager::DISPLAY_NAMES.insert(membership.stateKey(), displayName);
+                TimelineViewManager::DISPLAY_NAMES.insert(stateKey, displayName);
 }
 
 void
-ChatPage::removeLeftRooms(const QMap<QString, LeftRoom> &rooms)
+ChatPage::removeLeftRooms(const std::map<std::string, mtx::responses::LeftRoom> &rooms)
 {
-        for (auto it = rooms.constBegin(); it != rooms.constEnd(); ++it) {
-                if (state_manager_.contains(it.key()))
-                        removeRoom(it.key());
+        for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
+                const auto room_id = QString::fromStdString(it->first);
+
+                if (state_manager_.contains(room_id))
+                        removeRoom(room_id);
         }
 }
 
 void
-ChatPage::updateJoinedRooms(const QMap<QString, JoinedRoom> &rooms)
+ChatPage::updateJoinedRooms(const std::map<std::string, mtx::responses::JoinedRoom> &rooms)
 {
-        for (auto it = rooms.constBegin(); it != rooms.constEnd(); ++it) {
-                updateTypingUsers(it.key(), it.value().typingUserIDs());
+        for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
+                const auto roomid = QString::fromStdString(it->first);
+
+                updateTypingUsers(roomid, it->second.ephemeral.typing);
 
-                const auto newStateEvents    = it.value().state().events();
-                const auto newTimelineEvents = it.value().timeline().events();
+                const auto newStateEvents    = it->second.state;
+                const auto newTimelineEvents = it->second.timeline;
 
                 // Merge the new updates for rooms that we are tracking.
-                if (state_manager_.contains(it.key())) {
-                        auto oldState = &state_manager_[it.key()];
-                        oldState->updateFromEvents(newStateEvents);
-                        oldState->updateFromEvents(newTimelineEvents);
+                if (state_manager_.contains(roomid)) {
+                        auto oldState = &state_manager_[roomid];
+                        oldState->updateFromEvents(newStateEvents.events);
+                        oldState->updateFromEvents(newTimelineEvents.events);
                         oldState->resolveName();
                         oldState->resolveAvatar();
                 } else {
                         // Build the current state from the timeline and state events.
                         RoomState room_state;
-                        room_state.updateFromEvents(newStateEvents);
-                        room_state.updateFromEvents(newTimelineEvents);
+                        room_state.updateFromEvents(newStateEvents.events);
+                        room_state.updateFromEvents(newTimelineEvents.events);
 
                         // Resolve room name and avatar. e.g in case of one-to-one chats.
                         room_state.resolveName();
                         room_state.resolveAvatar();
 
-                        state_manager_.insert(it.key(), room_state);
+                        state_manager_.insert(roomid, room_state);
 
                         settingsManager_.insert(
-                          it.key(), QSharedPointer<RoomSettings>(new RoomSettings(it.key())));
+                          roomid, QSharedPointer<RoomSettings>(new RoomSettings(roomid)));
 
-                        view_manager_->addRoom(it.value(), it.key());
+                        view_manager_->addRoom(it->second, roomid);
                 }
 
-                updateUserMetadata(newStateEvents);
-                updateUserMetadata(newTimelineEvents);
+                updateUserMetadata(newStateEvents.events);
+                updateUserMetadata(newTimelineEvents.events);
 
-                if (it.key() == current_room_)
-                        changeTopRoomInfo(it.key());
+                if (roomid == current_room_)
+                        changeTopRoomInfo(roomid);
 
                 QApplication::processEvents();
         }
 }
 
 QMap<QString, RoomState>
-ChatPage::generateMembershipDifference(const QMap<QString, JoinedRoom> &rooms,
-                                       const QMap<QString, RoomState> &states) const
+ChatPage::generateMembershipDifference(
+  const std::map<std::string, mtx::responses::JoinedRoom> &rooms,
+  const QMap<QString, RoomState> &states) const
 {
         QMap<QString, RoomState> stateDiff;
 
-        for (auto it = rooms.constBegin(); it != rooms.constEnd(); ++it) {
-                if (!states.contains(it.key()))
+        for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
+                const auto room_id = QString::fromStdString(it->first);
+
+                if (!states.contains(room_id))
                         continue;
 
-                auto events = it.value().state().events();
+                auto all_memberships     = getMemberships(it->second.state.events);
+                auto timelineMemberships = getMemberships(it->second.timeline.events);
 
-                for (auto event : it.value().timeline().events())
-                        events.append(event);
+                // We have to process first the state events and then the timeline.
+                for (auto mm = timelineMemberships.cbegin(); mm != timelineMemberships.cend(); ++mm)
+                        all_memberships.emplace(mm->first, mm->second);
 
                 RoomState local;
-                local.aliases            = states[it.key()].aliases;
-                local.avatar             = states[it.key()].avatar;
-                local.canonical_alias    = states[it.key()].canonical_alias;
-                local.history_visibility = states[it.key()].history_visibility;
-                local.join_rules         = states[it.key()].join_rules;
-                local.name               = states[it.key()].name;
-                local.power_levels       = states[it.key()].power_levels;
-                local.topic              = states[it.key()].topic;
-                local.memberships        = getMemberships(events);
-
-                stateDiff.insert(it.key(), local);
+                local.aliases            = states[room_id].aliases;
+                local.avatar             = states[room_id].avatar;
+                local.canonical_alias    = states[room_id].canonical_alias;
+                local.history_visibility = states[room_id].history_visibility;
+                local.join_rules         = states[room_id].join_rules;
+                local.name               = states[room_id].name;
+                local.power_levels       = states[room_id].power_levels;
+                local.topic              = states[room_id].topic;
+                local.memberships        = all_memberships;
+
+                stateDiff.insert(room_id, local);
         }
 
         return stateDiff;
 }
 
-using Memberships = QMap<QString, matrix::events::StateEvent<events::MemberEventContent>>;
-
-Memberships
-ChatPage::getMemberships(const QJsonArray &events) const
-{
-        Memberships memberships;
-
-        events::EventType ty;
-
-        for (const auto &event : events) {
-                try {
-                        ty = events::extractEventType(event.toObject());
-                } catch (const DeserializationException &e) {
-                        qWarning() << e.what() << event;
-                        continue;
-                }
-
-                if (!events::isStateEvent(ty))
-                        continue;
-
-                try {
-                        switch (ty) {
-                        case events::EventType::RoomMember: {
-                                events::StateEvent<events::MemberEventContent> member;
-                                member.deserialize(event);
-                                memberships.insert(member.stateKey(), member);
-                                break;
-                        }
-                        default: {
-                                continue;
-                        }
-                        }
-                } catch (const DeserializationException &e) {
-                        qWarning() << e.what() << event;
-                        continue;
-                }
-        }
-
-        return memberships;
-}
-
 ChatPage::~ChatPage() {}
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index e4dcb554..3a987d27 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -30,12 +30,7 @@
 
 #include "Login.h"
 #include "MatrixClient.h"
-#include "MessageEvent.h"
 #include "Register.h"
-#include "RoomMessages.h"
-#include "Sync.h"
-
-#include "mtx.hpp"
 
 MatrixClient::MatrixClient(QString server, QObject *parent)
   : QNetworkAccessManager(parent)
@@ -239,26 +234,17 @@ MatrixClient::sync() noexcept
                         return;
                 }
 
-                auto data = reply->readAll();
-
-                if (data.isEmpty())
-                        return;
-
-                auto json = QJsonDocument::fromJson(data);
-
-                SyncResponse response;
-
                 try {
-                        response.deserialize(json);
+                        mtx::responses::Sync response = nlohmann::json::parse(reply->readAll());
                         emit syncCompleted(response);
-                } catch (DeserializationException &e) {
+                } catch (std::exception &e) {
                         qWarning() << "Sync malformed response" << e.what();
                 }
         });
 }
 
 void
-MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty,
+MatrixClient::sendRoomMessage(mtx::events::MessageType ty,
                               int txnId,
                               const QString &roomid,
                               const QString &msg,
@@ -283,19 +269,19 @@ MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty,
         QJsonObject info = {{"size", fileinfo.size()}, {"mimetype", mime.name()}};
 
         switch (ty) {
-        case matrix::events::MessageEventType::Text:
+        case mtx::events::MessageType::Text:
                 body = {{"msgtype", "m.text"}, {"body", msg}};
                 break;
-        case matrix::events::MessageEventType::Emote:
+        case mtx::events::MessageType::Emote:
                 body = {{"msgtype", "m.emote"}, {"body", msg}};
                 break;
-        case matrix::events::MessageEventType::Image:
+        case mtx::events::MessageType::Image:
                 body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}, {"info", info}};
                 break;
-        case matrix::events::MessageEventType::File:
+        case mtx::events::MessageType::File:
                 body = {{"msgtype", "m.file"}, {"body", msg}, {"url", url}, {"info", info}};
                 break;
-        case matrix::events::MessageEventType::Audio:
+        case mtx::events::MessageType::Audio:
                 body = {{"msgtype", "m.audio"}, {"body", msg}, {"url", url}, {"info", info}};
                 break;
         default:
@@ -371,23 +357,14 @@ MatrixClient::initialSync() noexcept
                         return;
                 }
 
-                auto data = reply->readAll();
-
-                if (data.isEmpty())
-                        return;
-
-                auto json = QJsonDocument::fromJson(data);
-
-                SyncResponse response;
-
                 try {
-                        response.deserialize(json);
+                        mtx::responses::Sync response = nlohmann::json::parse(reply->readAll());
+                        emit initialSyncCompleted(response);
                 } catch (DeserializationException &e) {
                         qWarning() << "Sync malformed response" << e.what();
                         return;
                 }
 
-                emit initialSyncCompleted(response);
         });
 }
 
@@ -686,18 +663,15 @@ MatrixClient::messages(const QString &roomid, const QString &from_token, int lim
                         return;
                 }
 
-                auto data = reply->readAll();
-
-                RoomMessages msgs;
-
                 try {
-                        msgs.deserialize(QJsonDocument::fromJson(data));
-                } catch (const DeserializationException &e) {
+                        mtx::responses::Messages messages =
+                          nlohmann::json::parse(reply->readAll().data());
+
+                        emit messagesRetrieved(roomid, messages);
+                } catch (std::exception &e) {
                         qWarning() << "Room messages from" << roomid << e.what();
                         return;
                 }
-
-                emit messagesRetrieved(roomid, msgs);
         });
 }
 
diff --git a/src/RoomList.cc b/src/RoomList.cc
index 23c0c5a7..a892e406 100644
--- a/src/RoomList.cc
+++ b/src/RoomList.cc
@@ -25,7 +25,6 @@
 #include "RoomList.h"
 #include "RoomSettings.h"
 #include "RoomState.h"
-#include "Sync.h"
 
 RoomList::RoomList(QSharedPointer<MatrixClient> client, QWidget *parent)
   : QWidget(parent)
diff --git a/src/RoomState.cc b/src/RoomState.cc
index 2b8bcdba..2bfea173 100644
--- a/src/RoomState.cc
+++ b/src/RoomState.cc
@@ -15,15 +15,19 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <QDebug>
 #include <QJsonArray>
+#include <QJsonObject>
 #include <QSettings>
 
 #include "RoomState.h"
 
-namespace events = matrix::events;
-
 RoomState::RoomState() {}
-RoomState::RoomState(const QJsonArray &events) { updateFromEvents(events); }
+RoomState::RoomState(const mtx::responses::Timeline &timeline)
+{
+        updateFromEvents(timeline.events);
+}
+RoomState::RoomState(const mtx::responses::State &state) { updateFromEvents(state.events); }
 
 void
 RoomState::resolveName()
@@ -31,19 +35,19 @@ RoomState::resolveName()
         name_ = "Empty Room";
         userAvatar_.clear();
 
-        if (!name.content().name().isEmpty()) {
-                name_ = name.content().name().simplified();
+        if (!name.content.name.empty()) {
+                name_ = QString::fromStdString(name.content.name).simplified();
                 return;
         }
 
-        if (!canonical_alias.content().alias().isEmpty()) {
-                name_ = canonical_alias.content().alias().simplified();
+        if (!canonical_alias.content.alias.empty()) {
+                name_ = QString::fromStdString(canonical_alias.content.alias).simplified();
                 return;
         }
 
         // FIXME: Doesn't follow the spec guidelines.
-        if (aliases.content().aliases().size() != 0) {
-                name_ = aliases.content().aliases()[0].simplified();
+        if (aliases.content.aliases.size() != 0) {
+                name_ = QString::fromStdString(aliases.content.aliases[0]).simplified();
                 return;
         }
 
@@ -52,16 +56,20 @@ RoomState::resolveName()
 
         // TODO: Display names should be sorted alphabetically.
         for (const auto membership : memberships) {
-                if (membership.stateKey() == user_id)
+                const auto stateKey = QString::fromStdString(membership.second.state_key);
+
+                if (stateKey == user_id)
                         continue;
 
-                if (membership.content().membershipState() == events::Membership::Join) {
-                        userAvatar_ = membership.stateKey();
+                if (membership.second.content.membership == mtx::events::state::Membership::Join) {
+                        userAvatar_ = stateKey;
+                        auto displayName =
+                          QString::fromStdString(membership.second.content.display_name);
 
-                        if (membership.content().displayName().isEmpty())
-                                name_ = membership.stateKey();
+                        if (displayName.isEmpty())
+                                name_ = stateKey;
                         else
-                                name_ = membership.content().displayName();
+                                name_ = displayName;
 
                         break;
                 }
@@ -76,12 +84,13 @@ void
 RoomState::resolveAvatar()
 {
         if (userAvatar_.isEmpty()) {
-                avatar_ = avatar.content().url();
+                avatar_ = QString::fromStdString(avatar.content.url);
                 return;
         }
 
-        if (memberships.contains(userAvatar_)) {
-                avatar_ = memberships[userAvatar_].content().avatarUrl();
+        if (memberships.count(userAvatar_.toStdString()) != 0) {
+                avatar_ =
+                  QString::fromStdString(memberships[userAvatar_.toStdString()].content.avatar_url);
         } else {
                 qWarning() << "Setting room avatar from unknown user id" << userAvatar_;
         }
@@ -91,8 +100,8 @@ RoomState::resolveAvatar()
 void
 RoomState::removeLeaveMemberships()
 {
-        for (auto it = memberships.begin(); it != memberships.end();) {
-                if (it.value().content().membershipState() == events::Membership::Leave) {
+        for (auto it = memberships.cbegin(); it != memberships.cend();) {
+                if (it->second.content.membership == mtx::events::state::Membership::Leave) {
                         it = memberships.erase(it);
                 } else {
                         ++it;
@@ -106,49 +115,51 @@ RoomState::update(const RoomState &state)
         bool needsNameCalculation   = false;
         bool needsAvatarCalculation = false;
 
-        if (aliases.eventId() != state.aliases.eventId()) {
+        if (aliases.event_id != state.aliases.event_id)
                 aliases = state.aliases;
-        }
 
-        if (avatar.eventId() != state.avatar.eventId()) {
+        if (avatar.event_id != state.avatar.event_id) {
                 avatar                 = state.avatar;
                 needsAvatarCalculation = true;
         }
 
-        if (canonical_alias.eventId() != state.canonical_alias.eventId()) {
+        if (canonical_alias.event_id != state.canonical_alias.event_id) {
                 canonical_alias      = state.canonical_alias;
                 needsNameCalculation = true;
         }
 
-        if (create.eventId() != state.create.eventId())
+        if (create.event_id != state.create.event_id)
                 create = state.create;
-        if (history_visibility.eventId() != state.history_visibility.eventId())
+
+        if (history_visibility.event_id != state.history_visibility.event_id)
                 history_visibility = state.history_visibility;
-        if (join_rules.eventId() != state.join_rules.eventId())
+
+        if (join_rules.event_id != state.join_rules.event_id)
                 join_rules = state.join_rules;
 
-        if (name.eventId() != state.name.eventId()) {
+        if (name.event_id != state.name.event_id) {
                 name                 = state.name;
                 needsNameCalculation = true;
         }
 
-        if (power_levels.eventId() != state.power_levels.eventId())
+        if (power_levels.event_id != state.power_levels.event_id)
                 power_levels = state.power_levels;
-        if (topic.eventId() != state.topic.eventId())
+
+        if (topic.event_id != state.topic.event_id)
                 topic = state.topic;
 
-        for (auto it = state.memberships.constBegin(); it != state.memberships.constEnd(); ++it) {
-                auto membershipState = it.value().content().membershipState();
+        for (auto it = state.memberships.cbegin(); it != state.memberships.cend(); ++it) {
+                auto membershipState = it->second.content.membership;
 
-                if (it.key() == userAvatar_) {
+                if (it->first == userAvatar_.toStdString()) {
                         needsNameCalculation   = true;
                         needsAvatarCalculation = true;
                 }
 
-                if (membershipState == events::Membership::Leave)
-                        this->memberships.remove(it.key());
+                if (membershipState == mtx::events::state::Membership::Leave)
+                        this->memberships.erase(this->memberships.find(it->first));
                 else
-                        this->memberships.insert(it.key(), it.value());
+                        this->memberships.emplace(it->first, it->second);
         }
 
         if (needsNameCalculation)
@@ -158,235 +169,126 @@ RoomState::update(const RoomState &state)
                 resolveAvatar();
 }
 
-QJsonObject
+std::string
 RoomState::serialize() const
 {
-        QJsonObject obj;
+        nlohmann::json obj;
 
-        if (!aliases.eventId().isEmpty())
-                obj["aliases"] = aliases.serialize();
+        if (!aliases.event_id.empty())
+                obj["aliases"] = aliases;
 
-        if (!avatar.eventId().isEmpty())
-                obj["avatar"] = avatar.serialize();
+        if (!avatar.event_id.empty())
+                obj["avatar"] = avatar;
 
-        if (!canonical_alias.eventId().isEmpty())
-                obj["canonical_alias"] = canonical_alias.serialize();
+        if (!canonical_alias.event_id.empty())
+                obj["canonical_alias"] = canonical_alias;
 
-        if (!create.eventId().isEmpty())
-                obj["create"] = create.serialize();
+        if (!create.event_id.empty())
+                obj["create"] = create;
 
-        if (!history_visibility.eventId().isEmpty())
-                obj["history_visibility"] = history_visibility.serialize();
+        if (!history_visibility.event_id.empty())
+                obj["history_visibility"] = history_visibility;
 
-        if (!join_rules.eventId().isEmpty())
-                obj["join_rules"] = join_rules.serialize();
+        if (!join_rules.event_id.empty())
+                obj["join_rules"] = join_rules;
 
-        if (!name.eventId().isEmpty())
-                obj["name"] = name.serialize();
+        if (!name.event_id.empty())
+                obj["name"] = name;
 
-        if (!power_levels.eventId().isEmpty())
-                obj["power_levels"] = power_levels.serialize();
+        if (!power_levels.event_id.empty())
+                obj["power_levels"] = power_levels;
 
-        if (!topic.eventId().isEmpty())
-                obj["topic"] = topic.serialize();
+        if (!topic.event_id.empty())
+                obj["topic"] = topic;
 
-        return obj;
+        return obj.dump();
 }
 
 void
-RoomState::parse(const QJsonObject &object)
+RoomState::parse(const nlohmann::json &object)
 {
-        // FIXME: Make this less versbose.
-
-        if (object.contains("aliases")) {
-                events::StateEvent<events::AliasesEventContent> event;
-
+        if (object.count("aliases") != 0) {
                 try {
-                        event.deserialize(object["aliases"]);
-                        aliases = event;
-                } catch (const DeserializationException &e) {
+                        aliases = object.at("aliases")
+                                    .get<mtx::events::StateEvent<mtx::events::state::Aliases>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - aliases" << e.what();
                 }
         }
 
-        if (object.contains("avatar")) {
-                events::StateEvent<events::AvatarEventContent> event;
-
+        if (object.count("avatar") != 0) {
                 try {
-                        event.deserialize(object["avatar"]);
-                        avatar = event;
-                } catch (const DeserializationException &e) {
+                        avatar = object.at("avatar")
+                                   .get<mtx::events::StateEvent<mtx::events::state::Avatar>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - avatar" << e.what();
                 }
         }
 
-        if (object.contains("canonical_alias")) {
-                events::StateEvent<events::CanonicalAliasEventContent> event;
-
+        if (object.count("canonical_alias") != 0) {
                 try {
-                        event.deserialize(object["canonical_alias"]);
-                        canonical_alias = event;
-                } catch (const DeserializationException &e) {
+                        canonical_alias =
+                          object.at("canonical_alias")
+                            .get<mtx::events::StateEvent<mtx::events::state::CanonicalAlias>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - canonical_alias" << e.what();
                 }
         }
 
-        if (object.contains("create")) {
-                events::StateEvent<events::CreateEventContent> event;
-
+        if (object.count("create") != 0) {
                 try {
-                        event.deserialize(object["create"]);
-                        create = event;
-                } catch (const DeserializationException &e) {
+                        create = object.at("create")
+                                   .get<mtx::events::StateEvent<mtx::events::state::Create>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - create" << e.what();
                 }
         }
 
-        if (object.contains("history_visibility")) {
-                events::StateEvent<events::HistoryVisibilityEventContent> event;
-
+        if (object.count("history_visibility") != 0) {
                 try {
-                        event.deserialize(object["history_visibility"]);
-                        history_visibility = event;
-                } catch (const DeserializationException &e) {
+                        history_visibility =
+                          object.at("history_visibility")
+                            .get<mtx::events::StateEvent<mtx::events::state::HistoryVisibility>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - history_visibility" << e.what();
                 }
         }
 
-        if (object.contains("join_rules")) {
-                events::StateEvent<events::JoinRulesEventContent> event;
-
+        if (object.count("join_rules") != 0) {
                 try {
-                        event.deserialize(object["join_rules"]);
-                        join_rules = event;
-                } catch (const DeserializationException &e) {
+                        join_rules =
+                          object.at("join_rules")
+                            .get<mtx::events::StateEvent<mtx::events::state::JoinRules>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - join_rules" << e.what();
                 }
         }
 
-        if (object.contains("name")) {
-                events::StateEvent<events::NameEventContent> event;
-
+        if (object.count("name") != 0) {
                 try {
-                        event.deserialize(object["name"]);
-                        name = event;
-                } catch (const DeserializationException &e) {
+                        name = object.at("name")
+                                 .get<mtx::events::StateEvent<mtx::events::state::Name>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - name" << e.what();
                 }
         }
 
-        if (object.contains("power_levels")) {
-                events::StateEvent<events::PowerLevelsEventContent> event;
-
+        if (object.count("power_levels") != 0) {
                 try {
-                        event.deserialize(object["power_levels"]);
-                        power_levels = event;
-                } catch (const DeserializationException &e) {
+                        power_levels =
+                          object.at("power_levels")
+                            .get<mtx::events::StateEvent<mtx::events::state::PowerLevels>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - power_levels" << e.what();
                 }
         }
 
-        if (object.contains("topic")) {
-                events::StateEvent<events::TopicEventContent> event;
-
+        if (object.count("topic") != 0) {
                 try {
-                        event.deserialize(object["topic"]);
-                        topic = event;
-                } catch (const DeserializationException &e) {
+                        topic = object.at("topic")
+                                  .get<mtx::events::StateEvent<mtx::events::state::Topic>>();
+                } catch (std::exception &e) {
                         qWarning() << "RoomState::parse - topic" << e.what();
                 }
         }
 }
-
-void
-RoomState::updateFromEvents(const QJsonArray &events)
-{
-        events::EventType ty;
-
-        for (const auto &event : events) {
-                try {
-                        ty = events::extractEventType(event.toObject());
-                } catch (const DeserializationException &e) {
-                        qWarning() << e.what() << event;
-                        continue;
-                }
-
-                if (!events::isStateEvent(ty))
-                        continue;
-
-                try {
-                        switch (ty) {
-                        case events::EventType::RoomAliases: {
-                                events::StateEvent<events::AliasesEventContent> aliases;
-                                aliases.deserialize(event);
-                                this->aliases = aliases;
-                                break;
-                        }
-                        case events::EventType::RoomAvatar: {
-                                events::StateEvent<events::AvatarEventContent> avatar;
-                                avatar.deserialize(event);
-                                this->avatar = avatar;
-                                break;
-                        }
-                        case events::EventType::RoomCanonicalAlias: {
-                                events::StateEvent<events::CanonicalAliasEventContent>
-                                  canonical_alias;
-                                canonical_alias.deserialize(event);
-                                this->canonical_alias = canonical_alias;
-                                break;
-                        }
-                        case events::EventType::RoomCreate: {
-                                events::StateEvent<events::CreateEventContent> create;
-                                create.deserialize(event);
-                                this->create = create;
-                                break;
-                        }
-                        case events::EventType::RoomHistoryVisibility: {
-                                events::StateEvent<events::HistoryVisibilityEventContent>
-                                  history_visibility;
-                                history_visibility.deserialize(event);
-                                this->history_visibility = history_visibility;
-                                break;
-                        }
-                        case events::EventType::RoomJoinRules: {
-                                events::StateEvent<events::JoinRulesEventContent> join_rules;
-                                join_rules.deserialize(event);
-                                this->join_rules = join_rules;
-                                break;
-                        }
-                        case events::EventType::RoomName: {
-                                events::StateEvent<events::NameEventContent> name;
-                                name.deserialize(event);
-                                this->name = name;
-                                break;
-                        }
-                        case events::EventType::RoomMember: {
-                                events::StateEvent<events::MemberEventContent> member;
-                                member.deserialize(event);
-
-                                this->memberships.insert(member.stateKey(), member);
-
-                                break;
-                        }
-                        case events::EventType::RoomPowerLevels: {
-                                events::StateEvent<events::PowerLevelsEventContent> power_levels;
-                                power_levels.deserialize(event);
-                                this->power_levels = power_levels;
-                                break;
-                        }
-                        case events::EventType::RoomTopic: {
-                                events::StateEvent<events::TopicEventContent> topic;
-                                topic.deserialize(event);
-                                this->topic = topic;
-                                break;
-                        }
-                        default: {
-                                continue;
-                        }
-                        }
-                } catch (const DeserializationException &e) {
-                        qWarning() << e.what() << event;
-                        continue;
-                }
-        }
-}
diff --git a/src/Sync.cc b/src/Sync.cc
deleted file mode 100644
index 416fa0c6..00000000
--- a/src/Sync.cc
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDebug>
-
-#include "Sync.h"
-
-void
-SyncResponse::deserialize(const QJsonDocument &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("Sync response is not a JSON object");
-
-        QJsonObject object = data.object();
-
-        if (!object.contains("next_batch"))
-                throw DeserializationException("Sync: missing next_batch parameter");
-
-        if (object.contains("rooms")) {
-                if (!object.value("rooms").isObject()) {
-                        throw DeserializationException("Sync: rooms is not a JSON object");
-                }
-                rooms_.deserialize(object.value("rooms"));
-        }
-
-        if (object.contains("presence")) {
-                if (!object.value("presence").isObject()) {
-                        throw DeserializationException("Sync: presence is not a JSON object");
-                }
-                // TODO: implement presence handling
-        }
-
-        if (object.contains("account_data")) {
-                if (!object.value("account_data").isObject()) {
-                        throw DeserializationException("Sync: account_data is not a JSON object");
-                }
-                // TODO: implement account_data handling
-        }
-
-        if (object.contains("to_device")) {
-                if (!object.value("to_device").isObject()) {
-                        throw DeserializationException("Sync: to_device is not a JSON object");
-                }
-                // TODO: implement to_device handling
-        }
-
-        // for device_lists updates (for e2e)
-        if (object.contains("device_lists")) {
-                if (!object.value("device_lists").isObject()) {
-                        throw DeserializationException("Sync: device_lists is not a JSON object");
-                }
-                // TODO: implement device_lists handling
-        }
-
-        next_batch_ = object.value("next_batch").toString();
-}
-
-void
-Rooms::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("Rooms value is not a JSON object");
-
-        QJsonObject object = data.toObject();
-
-        if (object.contains("join")) {
-                if (!object.value("join").isObject())
-                        throw DeserializationException("rooms/join must be a JSON object");
-
-                auto join = object.value("join").toObject();
-
-                for (auto it = join.constBegin(); it != join.constEnd(); ++it) {
-                        JoinedRoom tmp_room;
-                        try {
-                                tmp_room.deserialize(it.value());
-                                join_.insert(it.key(), tmp_room);
-                        } catch (DeserializationException &e) {
-                                qWarning() << e.what();
-                                qWarning() << "Skipping malformed object for room" << it.key();
-                        }
-                }
-        }
-
-        if (object.contains("invite")) {
-                if (!object.value("invite").isObject()) {
-                        throw DeserializationException("rooms/invite must be a JSON object");
-                }
-                // TODO: Implement invite handling
-        }
-
-        if (object.contains("leave")) {
-                if (!object.value("leave").isObject()) {
-                        throw DeserializationException("rooms/leave must be a JSON object");
-                }
-                auto leave = object.value("leave").toObject();
-
-                for (auto it = leave.constBegin(); it != leave.constEnd(); ++it) {
-                        LeftRoom tmp_room;
-
-                        try {
-                                tmp_room.deserialize(it.value());
-                                leave_.insert(it.key(), tmp_room);
-                        } catch (DeserializationException &e) {
-                                qWarning() << e.what();
-                                qWarning() << "Skipping malformed object for room" << it.key();
-                        }
-                }
-        }
-}
-
-void
-JoinedRoom::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("JoinedRoom is not a JSON object");
-
-        QJsonObject object = data.toObject();
-
-        if (object.contains("state")) {
-                if (!object.value("state").isObject()) {
-                        throw DeserializationException("join/state should be an object");
-                }
-
-                QJsonObject state = object.value("state").toObject();
-
-                if (state.contains("events")) {
-                        if (!state.value("events").isArray()) {
-                                throw DeserializationException(
-                                  "join/state/events should be an array");
-                        }
-
-                        state_.deserialize(state.value("events"));
-                }
-        }
-
-        if (object.contains("timeline")) {
-                if (!object.value("timeline").isObject())
-                        throw DeserializationException("join/timeline should be an object");
-                timeline_.deserialize(object.value("timeline"));
-        }
-
-        if (object.contains("ephemeral")) {
-                if (!object.value("ephemeral").isObject())
-                        throw DeserializationException("join/ephemeral should be an object");
-
-                QJsonObject ephemeral = object.value("ephemeral").toObject();
-
-                if (ephemeral.contains("events")) {
-                        if (!ephemeral.value("events").isArray())
-                                qWarning() << "join/ephemeral/events should be an array";
-
-                        auto ephemeralEvents = ephemeral.value("events").toArray();
-
-                        for (const auto e : ephemeralEvents) {
-                                auto obj = e.toObject();
-
-                                if (obj.contains("type") && obj.value("type") == "m.typing") {
-                                        auto ids = obj.value("content")
-                                                     .toObject()
-                                                     .value("user_ids")
-                                                     .toArray();
-
-                                        for (const auto uid : ids)
-                                                typingUserIDs_.push_back(uid.toString());
-                                }
-                        }
-                }
-        }
-
-        if (object.contains("account_data")) {
-                if (!object.value("account_data").isObject())
-                        throw DeserializationException("join/account_data is not a JSON object");
-                // TODO: Implement account_data handling
-        }
-
-        if (object.contains("unread_notifications")) {
-                if (!object.value("unread_notifications").isObject()) {
-                        throw DeserializationException(
-                          "join/unread_notifications is not a JSON object");
-                }
-
-                QJsonObject unreadNotifications = object.value("unread_notifications").toObject();
-
-                if (unreadNotifications.contains("highlight_count")) {
-                        // TODO: Implement unread_notifications handling
-                }
-                if (unreadNotifications.contains("notification_count")) {
-                        // TODO: Implement unread_notifications handling
-                }
-        }
-}
-
-void
-LeftRoom::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("LeftRoom is not a JSON object");
-
-        QJsonObject object = data.toObject();
-
-        if (!object.contains("state"))
-                throw DeserializationException("leave/state is missing");
-
-        if (!object.contains("timeline"))
-                throw DeserializationException("leave/timeline is missing");
-
-        if (!object.value("state").isObject())
-                throw DeserializationException("leave/state should be an object");
-
-        QJsonObject state = object.value("state").toObject();
-
-        if (!state.contains("events"))
-                throw DeserializationException("leave/state/events is missing");
-
-        state_.deserialize(state.value("events"));
-        timeline_.deserialize(object.value("timeline"));
-}
-
-void
-Event::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("Event is not a JSON object");
-
-        QJsonObject object = data.toObject();
-
-        if (!object.contains("content"))
-                throw DeserializationException("event/content is missing");
-
-        if (!object.contains("unsigned"))
-                throw DeserializationException("event/content is missing");
-
-        if (!object.contains("sender"))
-                throw DeserializationException("event/sender is missing");
-
-        if (!object.contains("event_id"))
-                throw DeserializationException("event/event_id is missing");
-
-        // TODO: Make this optional
-        /* if (!object.contains("state_key")) */
-        /* 	throw DeserializationException("event/state_key is missing"); */
-
-        if (!object.contains("type"))
-                throw DeserializationException("event/type is missing");
-
-        if (!object.contains("origin_server_ts"))
-                throw DeserializationException("event/origin_server_ts is missing");
-
-        content_  = object.value("content").toObject();
-        unsigned_ = object.value("unsigned").toObject();
-
-        sender_    = object.value("sender").toString();
-        state_key_ = object.value("state_key").toString();
-        type_      = object.value("type").toString();
-        event_id_  = object.value("event_id").toString();
-
-        origin_server_ts_ = object.value("origin_server_ts").toDouble();
-}
-
-void
-State::deserialize(const QJsonValue &data)
-{
-        if (!data.isArray())
-                throw DeserializationException("State is not a JSON array");
-
-        events_ = data.toArray();
-}
-
-void
-Timeline::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("Timeline is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (!object.contains("events"))
-                throw DeserializationException("timeline/events is missing");
-
-        if (!object.contains("prev_batch"))
-                throw DeserializationException("timeline/prev_batch is missing");
-
-        if (!object.contains("limited"))
-                throw DeserializationException("timeline/limited is missing");
-
-        prev_batch_ = object.value("prev_batch").toString();
-        limited_    = object.value("limited").toBool();
-
-        if (!object.value("events").isArray())
-                throw DeserializationException("timeline/events is not a JSON array");
-
-        events_ = object.value("events").toArray();
-}
diff --git a/src/events/AliasesEventContent.cc b/src/events/AliasesEventContent.cc
deleted file mode 100644
index 87acbade..00000000
--- a/src/events/AliasesEventContent.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QJsonArray>
-
-#include "AliasesEventContent.h"
-
-using namespace matrix::events;
-
-void
-AliasesEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("AliasesEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("aliases") == QJsonValue::Undefined)
-                throw DeserializationException("aliases key is missing");
-
-        auto aliases = object.value("aliases").toArray();
-
-        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
deleted file mode 100644
index fc58ad5c..00000000
--- a/src/events/AvatarEventContent.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDebug>
-
-#include "AvatarEventContent.h"
-
-using namespace matrix::events;
-
-void
-AvatarEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("AvatarEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("url") == QJsonValue::Undefined)
-                throw DeserializationException("url key is missing");
-
-        url_ = QUrl(object.value("url").toString());
-
-        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
deleted file mode 100644
index 189a0cb0..00000000
--- a/src/events/CanonicalAliasEventContent.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "CanonicalAliasEventContent.h"
-
-using namespace matrix::events;
-
-void
-CanonicalAliasEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("CanonicalAliasEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("alias") == QJsonValue::Undefined)
-                throw DeserializationException("alias key is missing");
-
-        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
deleted file mode 100644
index f28099c4..00000000
--- a/src/events/CreateEventContent.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "CreateEventContent.h"
-
-using namespace matrix::events;
-
-void
-CreateEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("CreateEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("creator") == QJsonValue::Undefined)
-                throw DeserializationException("creator key is missing");
-
-        creator_ = object.value("creator").toString();
-}
-
-QJsonObject
-CreateEventContent::serialize() const
-{
-        QJsonObject object;
-
-        if (!creator_.isEmpty())
-                object["creator"] = creator_;
-
-        return object;
-}
diff --git a/src/events/Event.cc b/src/events/Event.cc
deleted file mode 100644
index 7e5bd1db..00000000
--- a/src/events/Event.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "events/Event.h"
-
-#include "AliasesEventContent.h"
-#include "AvatarEventContent.h"
-#include "CanonicalAliasEventContent.h"
-#include "CreateEventContent.h"
-#include "Deserializable.h"
-#include "HistoryVisibilityEventContent.h"
-#include "JoinRulesEventContent.h"
-#include "MemberEventContent.h"
-#include "NameEventContent.h"
-#include "PowerLevelsEventContent.h"
-#include "TopicEventContent.h"
-
-matrix::events::EventType
-matrix::events::extractEventType(const QJsonObject &object)
-{
-        if (!object.contains("type"))
-                throw DeserializationException("Missing event type");
-
-        auto type = object.value("type").toString();
-
-        if (type == "m.room.aliases")
-                return EventType::RoomAliases;
-        else if (type == "m.room.avatar")
-                return EventType::RoomAvatar;
-        else if (type == "m.room.canonical_alias")
-                return EventType::RoomCanonicalAlias;
-        else if (type == "m.room.create")
-                return EventType::RoomCreate;
-        else if (type == "m.room.history_visibility")
-                return EventType::RoomHistoryVisibility;
-        else if (type == "m.room.join_rules")
-                return EventType::RoomJoinRules;
-        else if (type == "m.room.member")
-                return EventType::RoomMember;
-        else if (type == "m.room.message")
-                return EventType::RoomMessage;
-        else if (type == "m.room.name")
-                return EventType::RoomName;
-        else if (type == "m.room.power_levels")
-                return EventType::RoomPowerLevels;
-        else if (type == "m.room.topic")
-                return EventType::RoomTopic;
-        else
-                return EventType::Unsupported;
-}
-
-bool
-matrix::events::isStateEvent(EventType type)
-{
-        return type == EventType::RoomAliases || type == EventType::RoomAvatar ||
-               type == EventType::RoomCanonicalAlias || type == EventType::RoomCreate ||
-               type == EventType::RoomHistoryVisibility || type == EventType::RoomJoinRules ||
-               type == EventType::RoomMember || type == EventType::RoomName ||
-               type == EventType::RoomPowerLevels || type == EventType::RoomTopic;
-}
-
-bool
-matrix::events::isMessageEvent(EventType type)
-{
-        return type == EventType::RoomMessage;
-}
-
-void
-matrix::events::UnsignedData::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("UnsignedData is not a JSON object");
-
-        auto object = data.toObject();
-
-        transaction_id_ = object.value("transaction_id").toString();
-        age_            = object.value("age").toDouble();
-}
-
-QJsonObject
-matrix::events::UnsignedData::serialize() const
-{
-        QJsonObject object;
-
-        if (!transaction_id_.isEmpty())
-                object["transaction_id"] = transaction_id_;
-
-        if (age_ > 0)
-                object["age"] = age_;
-
-        return object;
-}
diff --git a/src/events/HistoryVisibilityEventContent.cc b/src/events/HistoryVisibilityEventContent.cc
deleted file mode 100644
index 7c0a149c..00000000
--- a/src/events/HistoryVisibilityEventContent.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "HistoryVisibilityEventContent.h"
-
-using namespace matrix::events;
-
-void
-HistoryVisibilityEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException(
-                  "HistoryVisibilityEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("history_visibility") == QJsonValue::Undefined)
-                throw DeserializationException("history_visibility key is missing");
-
-        auto value = object.value("history_visibility").toString();
-
-        if (value == "invited")
-                history_visibility_ = HistoryVisibility::Invited;
-        else if (value == "joined")
-                history_visibility_ = HistoryVisibility::Joined;
-        else if (value == "shared")
-                history_visibility_ = HistoryVisibility::Shared;
-        else if (value == "world_readable")
-                history_visibility_ = HistoryVisibility::WorldReadable;
-        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
deleted file mode 100644
index 4f5cf6ee..00000000
--- a/src/events/JoinRulesEventContent.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "JoinRulesEventContent.h"
-
-using namespace matrix::events;
-
-void
-JoinRulesEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("JoinRulesEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("join_rule") == QJsonValue::Undefined)
-                throw DeserializationException("join_rule key is missing");
-
-        auto value = object.value("join_rule").toString();
-
-        if (value == "invite")
-                join_rule_ = JoinRule::Invite;
-        else if (value == "knock")
-                join_rule_ = JoinRule::Knock;
-        else if (value == "private")
-                join_rule_ = JoinRule::Private;
-        else if (value == "public")
-                join_rule_ = JoinRule::Public;
-        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
deleted file mode 100644
index f80130e6..00000000
--- a/src/events/MemberEventContent.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDebug>
-
-#include "MemberEventContent.h"
-
-using namespace matrix::events;
-
-void
-MemberEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("MemberEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (!object.contains("membership"))
-                throw DeserializationException("membership key is missing");
-
-        auto value = object.value("membership").toString();
-
-        if (value == "ban")
-                membership_state_ = Membership::Ban;
-        else if (value == "invite")
-                membership_state_ = Membership::Invite;
-        else if (value == "join")
-                membership_state_ = Membership::Join;
-        else if (value == "knock")
-                membership_state_ = Membership::Knock;
-        else if (value == "leave")
-                membership_state_ = Membership::Leave;
-        else
-                throw DeserializationException(
-                  QString("Unknown membership value: %1").arg(value).toUtf8().constData());
-
-        if (object.contains("avatar_url"))
-                avatar_url_ = QUrl(object.value("avatar_url").toString());
-
-        if (!avatar_url_.toString().isEmpty() && !avatar_url_.isValid())
-                qWarning() << "Invalid avatar url" << avatar_url_;
-
-        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
deleted file mode 100644
index fcf25da1..00000000
--- a/src/events/MessageEventContent.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDebug>
-
-#include "MessageEventContent.h"
-
-using namespace matrix::events;
-
-MessageEventType
-matrix::events::extractMessageEventType(const QJsonObject &data)
-{
-        if (!data.contains("content"))
-                return MessageEventType::Unknown;
-
-        auto content = data.value("content").toObject();
-        auto msgtype = content.value("msgtype").toString();
-
-        if (msgtype == "m.audio")
-                return MessageEventType::Audio;
-        else if (msgtype == "m.emote")
-                return MessageEventType::Emote;
-        else if (msgtype == "m.file")
-                return MessageEventType::File;
-        else if (msgtype == "m.image")
-                return MessageEventType::Image;
-        else if (msgtype == "m.location")
-                return MessageEventType::Location;
-        else if (msgtype == "m.notice")
-                return MessageEventType::Notice;
-        else if (msgtype == "m.text")
-                return MessageEventType::Text;
-        else if (msgtype == "m.video")
-                return MessageEventType::Video;
-        else
-                return MessageEventType::Unknown;
-}
-
-void
-MessageEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("MessageEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (!object.contains("body"))
-                throw DeserializationException("body key is missing");
-
-        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
deleted file mode 100644
index 45759cf2..00000000
--- a/src/events/NameEventContent.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "NameEventContent.h"
-
-using namespace matrix::events;
-
-void
-NameEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("NameEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("name") == QJsonValue::Undefined)
-                throw DeserializationException("name key is missing");
-
-        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
deleted file mode 100644
index c796f81f..00000000
--- a/src/events/PowerLevelsEventContent.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QJsonObject>
-
-#include "Deserializable.h"
-#include "PowerLevelsEventContent.h"
-
-using namespace matrix::events;
-
-void
-PowerLevelsEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("PowerLevelsEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("ban") != QJsonValue::Undefined)
-                ban_ = object.value("ban").toInt();
-
-        if (object.value("invite") != QJsonValue::Undefined)
-                invite_ = object.value("invite").toInt();
-
-        if (object.value("kick") != QJsonValue::Undefined)
-                kick_ = object.value("kick").toInt();
-
-        if (object.value("redact") != QJsonValue::Undefined)
-                redact_ = object.value("redact").toInt();
-
-        if (object.value("events_default") != QJsonValue::Undefined)
-                events_default_ = object.value("events_default").toInt();
-
-        if (object.value("state_default") != QJsonValue::Undefined)
-                state_default_ = object.value("state_default").toInt();
-
-        if (object.value("users_default") != QJsonValue::Undefined)
-                users_default_ = object.value("users_default").toInt();
-
-        if (object.value("users").isObject()) {
-                auto users = object.value("users").toObject();
-
-                for (auto it = users.constBegin(); it != users.constEnd(); ++it)
-                        users_.insert(it.key(), it.value().toInt());
-        }
-
-        if (object.value("events").isObject()) {
-                auto events = object.value("events").toObject();
-
-                for (auto it = events.constBegin(); it != events.constEnd(); ++it)
-                        events_.insert(it.key(), it.value().toInt());
-        }
-}
-
-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))
-                return events_[event_type];
-
-        return events_default_;
-}
-
-int
-PowerLevelsEventContent::userLevel(QString userid) const
-{
-        if (users_.contains(userid))
-                return users_[userid];
-
-        return users_default_;
-}
diff --git a/src/events/TopicEventContent.cc b/src/events/TopicEventContent.cc
deleted file mode 100644
index 89602ded..00000000
--- a/src/events/TopicEventContent.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "TopicEventContent.h"
-
-using namespace matrix::events;
-
-void
-TopicEventContent::deserialize(const QJsonValue &data)
-{
-        if (!data.isObject())
-                throw DeserializationException("TopicEventContent is not a JSON object");
-
-        auto object = data.toObject();
-
-        if (object.value("topic") == QJsonValue::Undefined)
-                throw DeserializationException("topic key is missing");
-
-        topic_ = object.value("topic").toString();
-}
-
-QJsonObject
-TopicEventContent::serialize() const
-{
-        QJsonObject object;
-
-        if (!topic_.isEmpty())
-                object["topic"] = topic_;
-
-        return object;
-}
diff --git a/src/events/messages/Audio.cc b/src/events/messages/Audio.cc
deleted file mode 100644
index 6b8f8c7e..00000000
--- a/src/events/messages/Audio.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Audio.h"
-
-using namespace matrix::events::messages;
-
-void
-Audio::deserialize(const QJsonObject &object)
-{
-        if (!object.contains("url"))
-                throw DeserializationException("url key is missing");
-
-        url_ = object.value("url").toString();
-
-        if (object.value("msgtype") != "m.audio")
-                throw DeserializationException("invalid msgtype for audio");
-
-        if (object.contains("info")) {
-                auto info = object.value("info").toObject();
-
-                info_.duration = info.value("duration").toInt();
-                info_.mimetype = info.value("mimetype").toString();
-                info_.size     = info.value("size").toInt();
-        }
-}
diff --git a/src/events/messages/Emote.cc b/src/events/messages/Emote.cc
deleted file mode 100644
index 2c9ab823..00000000
--- a/src/events/messages/Emote.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Emote.h"
-
-using namespace matrix::events::messages;
-
-void
-Emote::deserialize(const QJsonObject &object)
-{
-        if (object.value("msgtype") != "m.emote")
-                throw DeserializationException("invalid msgtype for emote");
-}
diff --git a/src/events/messages/File.cc b/src/events/messages/File.cc
deleted file mode 100644
index 28bce441..00000000
--- a/src/events/messages/File.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "File.h"
-
-using namespace matrix::events::messages;
-
-void
-File::deserialize(const QJsonObject &object)
-{
-        if (!object.contains("url"))
-                throw DeserializationException("messages::File url key is missing");
-
-        if (object.value("msgtype") != "m.file")
-                throw DeserializationException("invalid msgtype for file");
-
-        url_      = object.value("url").toString();
-        filename_ = object.value("filename").toString();
-
-        if (object.contains("info")) {
-                auto file_info = object.value("info").toObject();
-
-                info_.size          = file_info.value("size").toInt();
-                info_.mimetype      = file_info.value("mimetype").toString();
-                info_.thumbnail_url = file_info.value("thumbnail_url").toString();
-
-                if (file_info.contains("thumbnail_info")) {
-                        auto thumbinfo = file_info.value("thumbnail_info").toObject();
-
-                        info_.thumbnail_info.h        = thumbinfo.value("h").toInt();
-                        info_.thumbnail_info.w        = thumbinfo.value("w").toInt();
-                        info_.thumbnail_info.size     = thumbinfo.value("size").toInt();
-                        info_.thumbnail_info.mimetype = thumbinfo.value("mimetype").toString();
-                }
-        }
-}
diff --git a/src/events/messages/Image.cc b/src/events/messages/Image.cc
deleted file mode 100644
index 9d7c7a3b..00000000
--- a/src/events/messages/Image.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Image.h"
-
-using namespace matrix::events::messages;
-
-void
-Image::deserialize(const QJsonObject &object)
-{
-        if (!object.contains("url"))
-                throw DeserializationException("messages::Image url key is missing");
-
-        url_ = object.value("url").toString();
-
-        if (object.value("msgtype") != "m.image")
-                throw DeserializationException("invalid msgtype for image");
-
-        if (object.contains("info")) {
-                auto imginfo = object.value("info").toObject();
-
-                info_.w    = imginfo.value("w").toInt();
-                info_.h    = imginfo.value("h").toInt();
-                info_.size = imginfo.value("size").toInt();
-
-                info_.mimetype      = imginfo.value("mimetype").toString();
-                info_.thumbnail_url = imginfo.value("thumbnail_url").toString();
-
-                if (imginfo.contains("thumbnail_info")) {
-                        auto thumbinfo = imginfo.value("thumbnail_info").toObject();
-
-                        info_.thumbnail_info.h        = thumbinfo.value("h").toInt();
-                        info_.thumbnail_info.w        = thumbinfo.value("w").toInt();
-                        info_.thumbnail_info.size     = thumbinfo.value("size").toInt();
-                        info_.thumbnail_info.mimetype = thumbinfo.value("mimetype").toString();
-                }
-        }
-}
diff --git a/src/events/messages/Location.cc b/src/events/messages/Location.cc
deleted file mode 100644
index ea2b333a..00000000
--- a/src/events/messages/Location.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Location.h"
-
-using namespace matrix::events::messages;
-
-void
-Location::deserialize(const QJsonObject &object)
-{
-        if (!object.contains("geo_uri"))
-                throw DeserializationException("messages::Location geo_uri key is missing");
-
-        if (object.value("msgtype") != "m.location")
-                throw DeserializationException("invalid msgtype for location");
-
-        geo_uri_ = object.value("geo_uri").toString();
-
-        if (object.contains("info")) {
-                auto location_info = object.value("info").toObject();
-
-                info_.thumbnail_url = location_info.value("thumbnail_url").toString();
-
-                if (location_info.contains("thumbnail_info")) {
-                        auto thumbinfo = location_info.value("thumbnail_info").toObject();
-
-                        info_.thumbnail_info.h        = thumbinfo.value("h").toInt();
-                        info_.thumbnail_info.w        = thumbinfo.value("w").toInt();
-                        info_.thumbnail_info.size     = thumbinfo.value("size").toInt();
-                        info_.thumbnail_info.mimetype = thumbinfo.value("mimetype").toString();
-                }
-        }
-}
diff --git a/src/events/messages/Notice.cc b/src/events/messages/Notice.cc
deleted file mode 100644
index 5b809c77..00000000
--- a/src/events/messages/Notice.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Notice.h"
-
-using namespace matrix::events::messages;
-
-void
-Notice::deserialize(const QJsonObject &object)
-{
-        if (object.value("msgtype") != "m.notice")
-                throw DeserializationException("invalid msgtype for notice");
-}
diff --git a/src/events/messages/Text.cc b/src/events/messages/Text.cc
deleted file mode 100644
index 6797ec01..00000000
--- a/src/events/messages/Text.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Text.h"
-
-using namespace matrix::events::messages;
-
-void
-Text::deserialize(const QJsonObject &object)
-{
-        if (object.value("msgtype") != "m.text")
-                throw DeserializationException("invalid msgtype for text");
-}
diff --git a/src/events/messages/Video.cc b/src/events/messages/Video.cc
deleted file mode 100644
index 89f90304..00000000
--- a/src/events/messages/Video.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * nheko Copyright (C) 2017  Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Video.h"
-
-using namespace matrix::events::messages;
-
-void
-Video::deserialize(const QJsonObject &object)
-{
-        if (!object.contains("url"))
-                throw DeserializationException("messages::Video url key is missing");
-
-        url_ = object.value("url").toString();
-
-        if (object.value("msgtype") != "m.video")
-                throw DeserializationException("invalid msgtype for video");
-
-        if (object.contains("info")) {
-                auto video_info = object.value("info").toObject();
-
-                info_.w        = video_info.value("w").toInt();
-                info_.h        = video_info.value("h").toInt();
-                info_.size     = video_info.value("size").toInt();
-                info_.duration = video_info.value("duration").toInt();
-
-                info_.mimetype      = video_info.value("mimetype").toString();
-                info_.thumbnail_url = video_info.value("thumbnail_url").toString();
-
-                if (video_info.contains("thumbnail_info")) {
-                        auto thumbinfo = video_info.value("thumbnail_info").toObject();
-
-                        info_.thumbnail_info.h        = thumbinfo.value("h").toInt();
-                        info_.thumbnail_info.w        = thumbinfo.value("w").toInt();
-                        info_.thumbnail_info.size     = thumbinfo.value("size").toInt();
-                        info_.thumbnail_info.mimetype = thumbinfo.value("mimetype").toString();
-                }
-        }
-}
diff --git a/src/timeline/TimelineItem.cc b/src/timeline/TimelineItem.cc
index 1cbe4b0a..39b345b5 100644
--- a/src/timeline/TimelineItem.cc
+++ b/src/timeline/TimelineItem.cc
@@ -21,7 +21,6 @@
 
 #include "Avatar.h"
 #include "Config.h"
-#include "Sync.h"
 
 #include "timeline/TimelineItem.h"
 #include "timeline/widgets/AudioItem.h"
@@ -32,9 +31,6 @@
 static const QRegExp URL_REGEX("((?:https?|ftp)://\\S+)");
 static const QString URL_HTML = "<a href=\"\\1\">\\1</a>";
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
-
 void
 TimelineItem::init()
 {
@@ -71,7 +67,7 @@ TimelineItem::init()
 /*
  * For messages created locally.
  */
-TimelineItem::TimelineItem(events::MessageEventType ty,
+TimelineItem::TimelineItem(mtx::events::MessageType ty,
                            const QString &userid,
                            QString body,
                            bool withSender,
@@ -83,7 +79,7 @@ TimelineItem::TimelineItem(events::MessageEventType ty,
         auto displayName = TimelineViewManager::displayName(userid);
         auto timestamp   = QDateTime::currentDateTime();
 
-        if (ty == events::MessageEventType::Emote) {
+        if (ty == mtx::events::MessageType::Emote) {
                 body            = QString("* %1 %2").arg(displayName).arg(body);
                 descriptionMsg_ = {"", userid, body, descriptiveTime(timestamp)};
         } else {
@@ -152,64 +148,65 @@ TimelineItem::TimelineItem(VideoItem *video,
 }
 
 TimelineItem::TimelineItem(ImageItem *image,
-                           const events::MessageEvent<msgs::Image> &event,
+                           const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
                            bool with_sender,
                            QWidget *parent)
   : QWidget(parent)
 {
-        setupWidgetLayout<events::MessageEvent<msgs::Image>, ImageItem>(
+        setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Image>, ImageItem>(
           image, event, " sent an image", with_sender);
 }
 
 TimelineItem::TimelineItem(FileItem *file,
-                           const events::MessageEvent<msgs::File> &event,
+                           const mtx::events::RoomEvent<mtx::events::msg::File> &event,
                            bool with_sender,
                            QWidget *parent)
   : QWidget(parent)
 {
-        setupWidgetLayout<events::MessageEvent<msgs::File>, FileItem>(
+        setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::File>, FileItem>(
           file, event, " sent a file", with_sender);
 }
 
 TimelineItem::TimelineItem(AudioItem *audio,
-                           const events::MessageEvent<msgs::Audio> &event,
+                           const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
                            bool with_sender,
                            QWidget *parent)
   : QWidget(parent)
 {
-        setupWidgetLayout<events::MessageEvent<msgs::Audio>, AudioItem>(
+        setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Audio>, AudioItem>(
           audio, event, " sent an audio clip", with_sender);
 }
 
 TimelineItem::TimelineItem(VideoItem *video,
-                           const events::MessageEvent<msgs::Video> &event,
+                           const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
                            bool with_sender,
                            QWidget *parent)
   : QWidget(parent)
 {
-        setupWidgetLayout<events::MessageEvent<msgs::Video>, VideoItem>(
+        setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Video>, VideoItem>(
           video, event, " sent a video clip", with_sender);
 }
 
 /*
  * Used to display remote notice messages.
  */
-TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
+TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &event,
                            bool with_sender,
                            QWidget *parent)
   : QWidget(parent)
 {
         init();
 
-        event_id_ = event.eventId();
+        event_id_         = QString::fromStdString(event.event_id);
+        const auto sender = QString::fromStdString(event.sender);
 
-        descriptionMsg_ = {TimelineViewManager::displayName(event.sender()),
-                           event.sender(),
+        descriptionMsg_ = {TimelineViewManager::displayName(sender),
+                           sender,
                            " sent a notification",
-                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
+                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.origin_server_ts))};
 
-        auto body      = event.content().body().trimmed().toHtmlEscaped();
-        auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
+        auto body      = QString::fromStdString(event.content.body).trimmed().toHtmlEscaped();
+        auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
 
         generateTimestamp(timestamp);
 
@@ -218,14 +215,14 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
         body = "<i>" + body + "</i>";
 
         if (with_sender) {
-                auto displayName = TimelineViewManager::displayName(event.sender());
+                auto displayName = TimelineViewManager::displayName(sender);
 
                 generateBody(displayName, body);
                 setupAvatarLayout(displayName);
 
                 mainLayout_->addLayout(headerLayout_);
 
-                AvatarProvider::resolve(event.sender(), this);
+                AvatarProvider::resolve(sender, this);
         } else {
                 generateBody(body);
                 setupSimpleLayout();
@@ -237,24 +234,25 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
 /*
  * Used to display remote emote messages.
  */
-TimelineItem::TimelineItem(const events::MessageEvent<msgs::Emote> &event,
+TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &event,
                            bool with_sender,
                            QWidget *parent)
   : QWidget(parent)
 {
         init();
 
-        event_id_ = event.eventId();
+        event_id_         = QString::fromStdString(event.event_id);
+        const auto sender = QString::fromStdString(event.sender);
 
-        auto body        = event.content().body().trimmed();
-        auto timestamp   = QDateTime::fromMSecsSinceEpoch(event.timestamp());
-        auto displayName = TimelineViewManager::displayName(event.sender());
+        auto body        = QString::fromStdString(event.content.body).trimmed();
+        auto timestamp   = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
+        auto displayName = TimelineViewManager::displayName(sender);
         auto emoteMsg    = QString("* %1 %2").arg(displayName).arg(body);
 
         descriptionMsg_ = {"",
-                           event.sender(),
+                           sender,
                            emoteMsg,
-                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
+                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.origin_server_ts))};
 
         generateTimestamp(timestamp);
         emoteMsg = emoteMsg.toHtmlEscaped();
@@ -266,7 +264,7 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Emote> &event,
                 setupAvatarLayout(displayName);
                 mainLayout_->addLayout(headerLayout_);
 
-                AvatarProvider::resolve(event.sender(), this);
+                AvatarProvider::resolve(sender, this);
         } else {
                 generateBody(emoteMsg);
                 setupSimpleLayout();
@@ -278,24 +276,25 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Emote> &event,
 /*
  * Used to display remote text messages.
  */
-TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event,
+TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &event,
                            bool with_sender,
                            QWidget *parent)
   : QWidget(parent)
 {
         init();
 
-        event_id_ = event.eventId();
+        event_id_         = QString::fromStdString(event.event_id);
+        const auto sender = QString::fromStdString(event.sender);
 
-        auto body        = event.content().body().trimmed();
-        auto timestamp   = QDateTime::fromMSecsSinceEpoch(event.timestamp());
-        auto displayName = TimelineViewManager::displayName(event.sender());
+        auto body        = QString::fromStdString(event.content.body).trimmed();
+        auto timestamp   = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
+        auto displayName = TimelineViewManager::displayName(sender);
 
         QSettings settings;
-        descriptionMsg_ = {event.sender() == settings.value("auth/user_id") ? "You" : displayName,
-                           event.sender(),
+        descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
+                           sender,
                            QString(": %1").arg(body),
-                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
+                           descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.origin_server_ts))};
 
         generateTimestamp(timestamp);
 
@@ -309,7 +308,7 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event,
 
                 mainLayout_->addLayout(headerLayout_);
 
-                AvatarProvider::resolve(event.sender(), this);
+                AvatarProvider::resolve(sender, this);
         } else {
                 generateBody(body);
                 setupSimpleLayout();
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 6b7928db..8e9f5f7c 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -22,7 +22,6 @@
 #include "FloatingButton.h"
 #include "RoomMessages.h"
 #include "ScrollBar.h"
-#include "Sync.h"
 
 #include "timeline/TimelineView.h"
 #include "timeline/widgets/AudioItem.h"
@@ -30,23 +29,7 @@
 #include "timeline/widgets/ImageItem.h"
 #include "timeline/widgets/VideoItem.h"
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
-
-static bool
-isRedactedEvent(const QJsonObject &event)
-{
-        if (event.contains("redacted_because"))
-                return true;
-
-        if (event.contains("unsigned") &&
-            event.value("unsigned").toObject().contains("redacted_because"))
-                return true;
-
-        return false;
-}
-
-TimelineView::TimelineView(const Timeline &timeline,
+TimelineView::TimelineView(const mtx::responses::Timeline &timeline,
                            QSharedPointer<MatrixClient> client,
                            const QString &room_id,
                            QWidget *parent)
@@ -167,12 +150,12 @@ TimelineView::sliderMoved(int position)
 }
 
 void
-TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msgs)
+TimelineView::addBackwardsEvents(const QString &room_id, const mtx::responses::Messages &msgs)
 {
         if (room_id_ != room_id)
                 return;
 
-        if (msgs.chunk().count() == 0) {
+        if (msgs.chunk.size() == 0) {
                 isTimelineFinished = true;
                 return;
         }
@@ -186,12 +169,11 @@ TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msg
 
         // Parse in reverse order to determine where we should not show sender's
         // name.
-        auto ii = msgs.chunk().size();
+        auto ii = msgs.chunk.size();
         while (ii != 0) {
                 --ii;
 
-                TimelineItem *item =
-                  parseMessageEvent(msgs.chunk().at(ii).toObject(), TimelineDirection::Top);
+                TimelineItem *item = parseMessageEvent(msgs.chunk[ii], TimelineDirection::Top);
 
                 if (item != nullptr)
                         items.push_back(item);
@@ -210,11 +192,11 @@ TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msg
 
         QApplication::processEvents();
 
-        prev_batch_token_       = msgs.end();
+        prev_batch_token_       = QString::fromStdString(msgs.end);
         isPaginationInProgress_ = false;
 
         // Exclude the top stretch.
-        if (!msgs.chunk().isEmpty() && scroll_layout_->count() > 1)
+        if (msgs.chunk.size() != 0 && scroll_layout_->count() > 1)
                 notifyForLastEvent();
 
         // If this batch is the first being rendered (i.e the first and the last
@@ -224,63 +206,59 @@ TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msg
 }
 
 TimelineItem *
-TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection direction)
+TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents &event,
+                                TimelineDirection direction)
 {
-        events::EventType ty = events::extractEventType(event);
-
-        if (ty == events::EventType::RoomMessage) {
-                events::MessageEventType msg_type = events::extractMessageEventType(event);
-
-                using Audio  = events::MessageEvent<msgs::Audio>;
-                using Emote  = events::MessageEvent<msgs::Emote>;
-                using File   = events::MessageEvent<msgs::File>;
-                using Image  = events::MessageEvent<msgs::Image>;
-                using Notice = events::MessageEvent<msgs::Notice>;
-                using Text   = events::MessageEvent<msgs::Text>;
-                using Video  = events::MessageEvent<msgs::Video>;
-
-                if (msg_type == events::MessageEventType::Audio) {
-                        return processMessageEvent<Audio, AudioItem>(event, direction);
-                } else if (msg_type == events::MessageEventType::Emote) {
-                        return processMessageEvent<Emote>(event, direction);
-                } else if (msg_type == events::MessageEventType::File) {
-                        return processMessageEvent<File, FileItem>(event, direction);
-                } else if (msg_type == events::MessageEventType::Image) {
-                        return processMessageEvent<Image, ImageItem>(event, direction);
-                } else if (msg_type == events::MessageEventType::Notice) {
-                        return processMessageEvent<Notice>(event, direction);
-                } else if (msg_type == events::MessageEventType::Text) {
-                        return processMessageEvent<Text>(event, direction);
-                } else if (msg_type == events::MessageEventType::Video) {
-                        return processMessageEvent<Video, VideoItem>(event, direction);
-                } else if (msg_type == events::MessageEventType::Unknown) {
-                        // TODO Handle redacted messages.
-                        // Silenced for now.
-                        if (!isRedactedEvent(event))
-                                qWarning() << "Unknown message type" << event;
-
-                        return nullptr;
-                }
+        namespace msg     = mtx::events::msg;
+        using AudioEvent  = mtx::events::RoomEvent<msg::Audio>;
+        using EmoteEvent  = mtx::events::RoomEvent<msg::Emote>;
+        using FileEvent   = mtx::events::RoomEvent<msg::File>;
+        using ImageEvent  = mtx::events::RoomEvent<msg::Image>;
+        using NoticeEvent = mtx::events::RoomEvent<msg::Notice>;
+        using TextEvent   = mtx::events::RoomEvent<msg::Text>;
+        using VideoEvent  = mtx::events::RoomEvent<msg::Video>;
+
+        if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Audio>>(event)) {
+                auto audio = mpark::get<mtx::events::RoomEvent<msg::Audio>>(event);
+                return processMessageEvent<AudioEvent, AudioItem>(audio, direction);
+        } else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Emote>>(event)) {
+                auto emote = mpark::get<mtx::events::RoomEvent<msg::Emote>>(event);
+                return processMessageEvent<EmoteEvent>(emote, direction);
+        } else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::File>>(event)) {
+                auto file = mpark::get<mtx::events::RoomEvent<msg::File>>(event);
+                return processMessageEvent<FileEvent, FileItem>(file, direction);
+        } else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Image>>(event)) {
+                auto image = mpark::get<mtx::events::RoomEvent<msg::Image>>(event);
+                return processMessageEvent<ImageEvent, ImageItem>(image, direction);
+        } else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Notice>>(event)) {
+                auto notice = mpark::get<mtx::events::RoomEvent<msg::Notice>>(event);
+                return processMessageEvent<NoticeEvent>(notice, direction);
+        } else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Text>>(event)) {
+                auto text = mpark::get<mtx::events::RoomEvent<msg::Text>>(event);
+                return processMessageEvent<TextEvent>(text, direction);
+        } else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Video>>(event)) {
+                auto video = mpark::get<mtx::events::RoomEvent<msg::Video>>(event);
+                return processMessageEvent<VideoEvent, VideoItem>(video, direction);
         }
 
         return nullptr;
 }
 
 int
-TimelineView::addEvents(const Timeline &timeline)
+TimelineView::addEvents(const mtx::responses::Timeline &timeline)
 {
         int message_count = 0;
 
         QSettings settings;
         QString localUser = settings.value("auth/user_id").toString();
 
-        for (const auto &event : timeline.events()) {
-                TimelineItem *item = parseMessageEvent(event.toObject(), TimelineDirection::Bottom);
+        for (const auto &event : timeline.events) {
+                TimelineItem *item = parseMessageEvent(event, TimelineDirection::Bottom);
 
                 if (item != nullptr) {
                         addTimelineItem(item, TimelineDirection::Bottom);
 
-                        if (localUser != event.toObject().value("sender").toString())
+                        if (localUser != getEventSender(event))
                                 message_count += 1;
                 }
         }
@@ -290,15 +268,15 @@ TimelineView::addEvents(const Timeline &timeline)
         QApplication::processEvents();
 
         if (isInitialSync) {
-                prev_batch_token_ = timeline.previousBatch();
+                prev_batch_token_ = QString::fromStdString(timeline.prev_batch);
                 isInitialSync     = false;
         }
 
         // Exclude the top stretch.
-        if (!timeline.events().isEmpty() && scroll_layout_->count() > 1)
+        if (timeline.events.size() != 0 && scroll_layout_->count() > 1)
                 notifyForLastEvent();
 
-        if (isActiveWindow() && isVisible() && timeline.events().size() > 0)
+        if (isActiveWindow() && isVisible() && timeline.events.size() > 0)
                 readLastEvent();
 
         return message_count;
@@ -403,7 +381,7 @@ TimelineView::updatePendingMessage(int txn_id, QString event_id)
 }
 
 void
-TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString &body)
+TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
 {
         QSettings settings;
         auto user_id     = settings.value("auth/user_id").toString();
@@ -439,9 +417,9 @@ TimelineView::sendNextPendingMessage()
 
         PendingMessage &m = pending_msgs_.head();
         switch (m.ty) {
-        case matrix::events::MessageEventType::Audio:
-        case matrix::events::MessageEventType::Image:
-        case matrix::events::MessageEventType::File:
+        case mtx::events::MessageType::Audio:
+        case mtx::events::MessageType::Image:
+        case mtx::events::MessageType::File:
                 // FIXME: Improve the API
                 client_->sendRoomMessage(m.ty,
                                          m.txn_id,
@@ -573,3 +551,81 @@ TimelineView::event(QEvent *event)
 
         return QWidget::event(event);
 }
+
+QString
+TimelineView::getEventSender(const mtx::events::collections::TimelineEvents &event) const
+{
+        using Aliases           = mtx::events::StateEvent<mtx::events::state::Aliases>;
+        using Avatar            = mtx::events::StateEvent<mtx::events::state::Avatar>;
+        using CanonicalAlias    = mtx::events::StateEvent<mtx::events::state::CanonicalAlias>;
+        using Create            = mtx::events::StateEvent<mtx::events::state::Create>;
+        using HistoryVisibility = mtx::events::StateEvent<mtx::events::state::HistoryVisibility>;
+        using JoinRules         = mtx::events::StateEvent<mtx::events::state::JoinRules>;
+        using Member            = mtx::events::StateEvent<mtx::events::state::Member>;
+        using Name              = mtx::events::StateEvent<mtx::events::state::Name>;
+        using PowerLevels       = mtx::events::StateEvent<mtx::events::state::PowerLevels>;
+        using Topic             = mtx::events::StateEvent<mtx::events::state::Topic>;
+
+        using Audio  = mtx::events::RoomEvent<mtx::events::msg::Audio>;
+        using Emote  = mtx::events::RoomEvent<mtx::events::msg::Emote>;
+        using File   = mtx::events::RoomEvent<mtx::events::msg::File>;
+        using Image  = mtx::events::RoomEvent<mtx::events::msg::Image>;
+        using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
+        using Text   = mtx::events::RoomEvent<mtx::events::msg::Text>;
+        using Video  = mtx::events::RoomEvent<mtx::events::msg::Video>;
+
+        if (mpark::holds_alternative<Aliases>(event)) {
+                auto msg = mpark::get<Aliases>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Avatar>(event)) {
+                auto msg = mpark::get<Avatar>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<CanonicalAlias>(event)) {
+                auto msg = mpark::get<CanonicalAlias>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Create>(event)) {
+                auto msg = mpark::get<Create>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<HistoryVisibility>(event)) {
+                auto msg = mpark::get<HistoryVisibility>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<JoinRules>(event)) {
+                auto msg = mpark::get<JoinRules>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Name>(event)) {
+                auto msg = mpark::get<Name>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Member>(event)) {
+                auto msg = mpark::get<Member>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<PowerLevels>(event)) {
+                auto msg = mpark::get<PowerLevels>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Topic>(event)) {
+                auto msg = mpark::get<Topic>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Audio>(event)) {
+                auto msg = mpark::get<Audio>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Emote>(event)) {
+                auto msg = mpark::get<Emote>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<File>(event)) {
+                auto msg = mpark::get<File>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Image>(event)) {
+                auto msg = mpark::get<Image>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Notice>(event)) {
+                auto msg = mpark::get<Notice>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Text>(event)) {
+                auto msg = mpark::get<Text>(event);
+                return QString::fromStdString(msg.sender);
+        } else if (mpark::holds_alternative<Video>(event)) {
+                auto msg = mpark::get<Video>(event);
+                return QString::fromStdString(msg.sender);
+        }
+
+        return QString("");
+}
diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc
index 281cafcd..de1e1e32 100644
--- a/src/timeline/TimelineViewManager.cc
+++ b/src/timeline/TimelineViewManager.cc
@@ -23,7 +23,6 @@
 #include <QSettings>
 
 #include "MatrixClient.h"
-#include "Sync.h"
 
 #include "timeline/TimelineView.h"
 #include "timeline/TimelineViewManager.h"
@@ -72,7 +71,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
         auto room_id = active_room_;
         auto view    = views_[room_id];
 
-        view->addUserMessage(matrix::events::MessageEventType::Text, msg);
+        view->addUserMessage(mtx::events::MessageType::Text, msg);
 }
 
 void
@@ -81,7 +80,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
         auto room_id = active_room_;
         auto view    = views_[room_id];
 
-        view->addUserMessage(matrix::events::MessageEventType::Emote, msg);
+        view->addUserMessage(mtx::events::MessageType::Emote, msg);
 }
 
 void
@@ -96,7 +95,7 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
 
         auto view = views_[roomid];
 
-        view->addUserMessage<ImageItem, matrix::events::MessageEventType::Image>(url, filename);
+        view->addUserMessage<ImageItem, mtx::events::MessageType::Image>(url, filename);
 }
 
 void
@@ -111,7 +110,7 @@ TimelineViewManager::queueFileMessage(const QString &roomid,
 
         auto view = views_[roomid];
 
-        view->addUserMessage<FileItem, matrix::events::MessageEventType::File>(url, filename);
+        view->addUserMessage<FileItem, mtx::events::MessageType::File>(url, filename);
 }
 
 void
@@ -126,7 +125,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
 
         auto view = views_[roomid];
 
-        view->addUserMessage<AudioItem, matrix::events::MessageEventType::Audio>(url, filename);
+        view->addUserMessage<AudioItem, mtx::events::MessageType::Audio>(url, filename);
 }
 
 void
@@ -139,10 +138,10 @@ TimelineViewManager::clearAll()
 }
 
 void
-TimelineViewManager::initialize(const Rooms &rooms)
+TimelineViewManager::initialize(const mtx::responses::Rooms &rooms)
 {
-        for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); ++it) {
-                addRoom(it.value(), it.key());
+        for (auto it = rooms.join.cbegin(); it != rooms.join.cend(); ++it) {
+                addRoom(it->second, QString::fromStdString(it->first));
         }
 }
 
@@ -155,10 +154,10 @@ TimelineViewManager::initialize(const QList<QString> &rooms)
 }
 
 void
-TimelineViewManager::addRoom(const JoinedRoom &room, const QString &room_id)
+TimelineViewManager::addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id)
 {
         // Create a history view with the room events.
-        TimelineView *view = new TimelineView(room.timeline(), client_, room_id);
+        TimelineView *view = new TimelineView(room.timeline, client_, room_id);
         views_.insert(room_id, QSharedPointer<TimelineView>(view));
 
         connect(view,
@@ -195,10 +194,10 @@ TimelineViewManager::addRoom(const QString &room_id)
 }
 
 void
-TimelineViewManager::sync(const Rooms &rooms)
+TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
 {
-        for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); ++it) {
-                auto roomid = it.key();
+        for (auto it = rooms.join.cbegin(); it != rooms.join.cend(); ++it) {
+                auto roomid = QString::fromStdString(it->first);
 
                 if (!views_.contains(roomid)) {
                         qDebug() << "Ignoring event from unknown room" << roomid;
@@ -207,7 +206,7 @@ TimelineViewManager::sync(const Rooms &rooms)
 
                 auto view = views_.value(roomid);
 
-                int msgs_added = view->addEvents(it.value().timeline());
+                int msgs_added = view->addEvents(it->second.timeline);
 
                 if (msgs_added > 0) {
                         // TODO: When the app window gets active the current
diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cc
index 2a417b3e..5d9dd77b 100644
--- a/src/timeline/widgets/AudioItem.cc
+++ b/src/timeline/widgets/AudioItem.cc
@@ -26,9 +26,6 @@
 
 #include "timeline/widgets/AudioItem.h"
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
-
 constexpr int MaxWidth          = 400;
 constexpr int Height            = 70;
 constexpr int IconRadius        = 22;
@@ -77,15 +74,15 @@ AudioItem::init()
 }
 
 AudioItem::AudioItem(QSharedPointer<MatrixClient> client,
-                     const events::MessageEvent<msgs::Audio> &event,
+                     const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
                      QWidget *parent)
   : QWidget(parent)
-  , url_{event.msgContent().url()}
-  , text_{event.content().body()}
+  , url_{QUrl(QString::fromStdString(event.content.url))}
+  , text_{QString::fromStdString(event.content.body)}
   , event_{event}
   , client_{client}
 {
-        readableFileSize_ = calculateFileSize(event.msgContent().info().size);
+        readableFileSize_ = calculateFileSize(event.content.info.size);
 
         init();
 }
@@ -151,14 +148,14 @@ AudioItem::mousePressEvent(QMouseEvent *event)
                 if (filenameToSave_.isEmpty())
                         return;
 
-                client_->downloadFile(event_.eventId(), url_);
+                client_->downloadFile(QString::fromStdString(event_.event_id), url_);
         }
 }
 
 void
 AudioItem::fileDownloaded(const QString &event_id, const QByteArray &data)
 {
-        if (event_id != event_.eventId())
+        if (event_id != QString::fromStdString(event_.event_id))
                 return;
 
         try {
diff --git a/src/timeline/widgets/FileItem.cc b/src/timeline/widgets/FileItem.cc
index e4cc02b2..3c38dc31 100644
--- a/src/timeline/widgets/FileItem.cc
+++ b/src/timeline/widgets/FileItem.cc
@@ -26,9 +26,6 @@
 
 #include "timeline/widgets/FileItem.h"
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
-
 constexpr int MaxWidth           = 400;
 constexpr int Height             = 70;
 constexpr int IconRadius         = 22;
@@ -64,15 +61,15 @@ FileItem::init()
 }
 
 FileItem::FileItem(QSharedPointer<MatrixClient> client,
-                   const events::MessageEvent<msgs::File> &event,
+                   const mtx::events::RoomEvent<mtx::events::msg::File> &event,
                    QWidget *parent)
   : QWidget(parent)
-  , url_{event.msgContent().url()}
-  , text_{event.content().body()}
+  , url_{QString::fromStdString(event.content.url)}
+  , text_{QString::fromStdString(event.content.body)}
   , event_{event}
   , client_{client}
 {
-        readableFileSize_ = calculateFileSize(event.msgContent().info().size);
+        readableFileSize_ = calculateFileSize(event.content.info.size);
 
         init();
 }
@@ -138,7 +135,7 @@ FileItem::mousePressEvent(QMouseEvent *event)
                 if (filenameToSave_.isEmpty())
                         return;
 
-                client_->downloadFile(event_.eventId(), url_);
+                client_->downloadFile(QString::fromStdString(event_.event_id), url_);
         } else {
                 openUrl();
         }
@@ -147,7 +144,7 @@ FileItem::mousePressEvent(QMouseEvent *event)
 void
 FileItem::fileDownloaded(const QString &event_id, const QByteArray &data)
 {
-        if (event_id != event_.eventId())
+        if (event_id != QString::fromStdString(event_.event_id))
                 return;
 
         try {
diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cc
index c8cf8e23..46a4518c 100644
--- a/src/timeline/widgets/ImageItem.cc
+++ b/src/timeline/widgets/ImageItem.cc
@@ -25,11 +25,8 @@
 #include "dialogs/ImageOverlay.h"
 #include "timeline/widgets/ImageItem.h"
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
-
 ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
-                     const events::MessageEvent<msgs::Image> &event,
+                     const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
                      QWidget *parent)
   : QWidget(parent)
   , event_{event}
@@ -39,8 +36,8 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
         setCursor(Qt::PointingHandCursor);
         setAttribute(Qt::WA_Hover, true);
 
-        url_  = event.msgContent().url();
-        text_ = event.content().body();
+        url_  = QString::fromStdString(event.content.url);
+        text_ = QString::fromStdString(event.content.body);
 
         QList<QString> url_parts = url_.toString().split("mxc://");
 
@@ -53,7 +50,7 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
         url_                 = QString("%1/_matrix/media/r0/download/%2")
                  .arg(client_.data()->getHomeServer().toString(), media_params);
 
-        client_.data()->downloadImage(event.eventId(), url_);
+        client_.data()->downloadImage(QString::fromStdString(event.event_id), url_);
 
         connect(client_.data(),
                 SIGNAL(imageDownloaded(const QString &, const QPixmap &)),
@@ -91,7 +88,7 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
 void
 ImageItem::imageDownloaded(const QString &event_id, const QPixmap &img)
 {
-        if (event_id != event_.eventId())
+        if (event_id != QString::fromStdString(event_.event_id))
                 return;
 
         setImage(img);
diff --git a/src/timeline/widgets/VideoItem.cc b/src/timeline/widgets/VideoItem.cc
index 63cbc20c..1d67118a 100644
--- a/src/timeline/widgets/VideoItem.cc
+++ b/src/timeline/widgets/VideoItem.cc
@@ -21,9 +21,6 @@
 
 #include "timeline/widgets/VideoItem.h"
 
-namespace events = matrix::events;
-namespace msgs   = matrix::events::messages;
-
 void
 VideoItem::init()
 {
@@ -39,15 +36,15 @@ VideoItem::init()
 }
 
 VideoItem::VideoItem(QSharedPointer<MatrixClient> client,
-                     const events::MessageEvent<msgs::Video> &event,
+                     const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
                      QWidget *parent)
   : QWidget(parent)
-  , url_{event.msgContent().url()}
-  , text_{event.content().body()}
+  , url_{QString::fromStdString(event.content.url)}
+  , text_{QString::fromStdString(event.content.body)}
   , event_{event}
   , client_{client}
 {
-        readableFileSize_ = calculateFileSize(event.msgContent().info().size);
+        readableFileSize_ = calculateFileSize(event.content.info.size);
 
         init();
 
diff --git a/tests/event_collection.cc b/tests/event_collection.cc
deleted file mode 100644
index 0ba7d227..00000000
--- a/tests/event_collection.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-#include <gtest/gtest.h>
-
-#include <QJsonArray>
-#include <QJsonObject>
-
-#include "Event.h"
-#include "RoomEvent.h"
-#include "StateEvent.h"
-
-#include "AliasesEventContent.h"
-#include "AvatarEventContent.h"
-#include "CanonicalAliasEventContent.h"
-#include "CreateEventContent.h"
-#include "HistoryVisibilityEventContent.h"
-#include "JoinRulesEventContent.h"
-#include "MemberEventContent.h"
-#include "NameEventContent.h"
-#include "PowerLevelsEventContent.h"
-#include "TopicEventContent.h"
-
-using namespace matrix::events;
-
-TEST(EventCollection, Deserialize)
-{
-        auto events = QJsonArray{
-                QJsonObject{ { "content", QJsonObject{ { "name", "Name" } } },
-                             { "event_id", "$asdfafdf8af:matrix.org" },
-                             { "prev_content", QJsonObject{ { "name", "Previous Name" } } },
-                             { "room_id", "!aasdfaeae23r9:matrix.org" },
-                             { "sender", "@alice:matrix.org" },
-                             { "origin_server_ts", 1323238293289323LL },
-                             { "state_key", "" },
-                             { "type", "m.room.name" } },
-                QJsonObject{ { "content", QJsonObject{ { "topic", "Topic" } } },
-                             { "event_id", "$asdfafdf8af:matrix.org" },
-                             { "prev_content", QJsonObject{ { "topic", "Previous Topic" } } },
-                             { "room_id", "!aasdfaeae23r9:matrix.org" },
-                             { "state_key", "" },
-                             { "sender", "@alice:matrix.org" },
-                             { "origin_server_ts", 1323238293289323LL },
-                             { "type", "m.room.topic" } },
-        };
-
-        for (const auto &event : events) {
-                EventType ty = extractEventType(event.toObject());
-
-                if (ty == EventType::RoomName) {
-                        StateEvent<NameEventContent> name_event;
-                        name_event.deserialize(event);
-
-                        EXPECT_EQ(name_event.content().name(), "Name");
-                        EXPECT_EQ(name_event.previousContent().name(), "Previous Name");
-                } else if (ty == EventType::RoomTopic) {
-                        StateEvent<TopicEventContent> topic_event;
-                        topic_event.deserialize(event);
-
-                        EXPECT_EQ(topic_event.content().topic(), "Topic");
-                        EXPECT_EQ(topic_event.previousContent().topic(), "Previous Topic");
-                } else {
-                        ASSERT_EQ(false, true);
-                }
-        }
-}
-
-TEST(EventCollection, DeserializationException)
-{
-        // Using wrong event types.
-        auto events = QJsonArray{
-                QJsonObject{ { "content", QJsonObject{ { "name", "Name" } } },
-                             { "event_id", "$asdfafdf8af:matrix.org" },
-                             { "prev_content", QJsonObject{ { "name", "Previous Name" } } },
-                             { "room_id", "!aasdfaeae23r9:matrix.org" },
-                             { "sender", "@alice:matrix.org" },
-                             { "origin_server_ts", 1323238293289323LL },
-                             { "state_key", "" },
-                             { "type", "m.room.topic" } },
-                QJsonObject{ { "content", QJsonObject{ { "topic", "Topic" } } },
-                             { "event_id", "$asdfafdf8af:matrix.org" },
-                             { "prev_content", QJsonObject{ { "topic", "Previous Topic" } } },
-                             { "room_id", "!aasdfaeae23r9:matrix.org" },
-                             { "state_key", "" },
-                             { "sender", "@alice:matrix.org" },
-                             { "origin_server_ts", 1323238293289323LL },
-                             { "type", "m.room.name" } },
-        };
-
-        for (const auto &event : events) {
-                EventType ty = extractEventType(event.toObject());
-
-                if (ty == EventType::RoomName) {
-                        StateEvent<NameEventContent> name_event;
-
-                        try {
-                                name_event.deserialize(event);
-                        } catch (const DeserializationException &e) {
-                                ASSERT_STREQ("name key is missing", e.what());
-                        }
-
-                } else if (ty == EventType::RoomTopic) {
-                        StateEvent<TopicEventContent> topic_event;
-
-                        try {
-                                topic_event.deserialize(event);
-                        } catch (const DeserializationException &e) {
-                                ASSERT_STREQ("topic key is missing", e.what());
-                        }
-                } else {
-                        ASSERT_EQ(false, true);
-                }
-        }
-}
diff --git a/tests/events.cc b/tests/events.cc
deleted file mode 100644
index a4f6fedf..00000000
--- a/tests/events.cc
+++ /dev/null
@@ -1,707 +0,0 @@
-#include <QDebug>
-#include <QJsonArray>
-#include <gtest/gtest.h>
-
-#include "Event.h"
-#include "RoomEvent.h"
-#include "StateEvent.h"
-
-#include "AliasesEventContent.h"
-#include "AvatarEventContent.h"
-#include "CanonicalAliasEventContent.h"
-#include "CreateEventContent.h"
-#include "HistoryVisibilityEventContent.h"
-#include "JoinRulesEventContent.h"
-#include "MemberEventContent.h"
-#include "NameEventContent.h"
-#include "PowerLevelsEventContent.h"
-#include "TopicEventContent.h"
-
-using namespace matrix::events;
-
-TEST(BaseEvent, Deserialization)
-{
-        // NameEventContent
-        auto data =
-          QJsonObject{{"content", QJsonObject{{"name", "Room Name"}}}, {"type", "m.room.name"}};
-
-        Event<NameEventContent> name_event;
-        name_event.deserialize(data);
-        EXPECT_EQ(name_event.content().name(), "Room Name");
-        EXPECT_EQ(name_event.serialize(), data);
-
-        // TopicEventContent
-        data = QJsonObject{{"content", QJsonObject{{"topic", "Room Topic"}}},
-                           {"unsigned", QJsonObject{{"age", 22}, {"transaction_id", "randomid"}}},
-                           {"type", "m.room.topic"}};
-
-        Event<TopicEventContent> topic_event;
-        topic_event.deserialize(data);
-        EXPECT_EQ(topic_event.content().topic(), "Room Topic");
-        EXPECT_EQ(topic_event.unsignedData().age(), 22);
-        EXPECT_EQ(topic_event.unsignedData().transactionId(), "randomid");
-        EXPECT_EQ(topic_event.serialize(), data);
-
-        // AvatarEventContent
-        data = QJsonObject{
-          {"content", QJsonObject{{"url", "https://matrix.org"}}},
-          {"unsigned", QJsonObject{{"age", 1343434343}, {"transaction_id", "m33434.33"}}},
-          {"type", "m.room.avatar"}};
-
-        Event<AvatarEventContent> avatar_event;
-        avatar_event.deserialize(data);
-        EXPECT_EQ(avatar_event.content().url().toString(), "https://matrix.org");
-        EXPECT_EQ(avatar_event.unsignedData().age(), 1343434343);
-        EXPECT_EQ(avatar_event.unsignedData().transactionId(), "m33434.33");
-        EXPECT_EQ(avatar_event.serialize(), data);
-
-        // AliasesEventContent
-        data = QJsonObject{
-          {"content",
-           QJsonObject{{"aliases", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}}},
-          {"unsigned", QJsonObject{{"transaction_id", "m33434.33"}}},
-          {"type", "m.room.aliases"}};
-
-        Event<AliasesEventContent> aliases_event;
-        aliases_event.deserialize(data);
-        EXPECT_EQ(aliases_event.content().aliases().size(), 2);
-        EXPECT_EQ(aliases_event.unsignedData().transactionId(), "m33434.33");
-        EXPECT_EQ(aliases_event.serialize(), data);
-
-        // CreateEventContent
-        data = QJsonObject{{"content", QJsonObject{{"creator", "@alice:matrix.org"}}},
-                           {"unsigned", QJsonObject{{"age", 2233}}},
-                           {"type", "m.room.create"}};
-
-        Event<CreateEventContent> create_event;
-        create_event.deserialize(data);
-        EXPECT_EQ(create_event.content().creator(), "@alice:matrix.org");
-        EXPECT_EQ(create_event.unsignedData().age(), 2233);
-        EXPECT_EQ(create_event.serialize(), data);
-
-        // JoinRulesEventContent
-        data = QJsonObject{{"content", QJsonObject{{"join_rule", "private"}}},
-                           {"type", "m.room.join_rules"}};
-
-        Event<JoinRulesEventContent> 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)
-{
-        auto data =
-          QJsonObject{{"content", QJsonObject{{"rule", "private"}}}, {"type", "m.room.join_rules"}};
-
-        Event<JoinRulesEventContent> event1;
-        ASSERT_THROW(event1.deserialize(data), DeserializationException);
-
-        data = QJsonObject{{"contents", QJsonObject{{"join_rule", "private"}}},
-                           {"type", "m.room.join_rules"}};
-
-        Event<JoinRulesEventContent> event2;
-        ASSERT_THROW(event2.deserialize(data), DeserializationException);
-
-        data = QJsonObject{{"contents", QJsonObject{{"join_rule", "private"}}},
-                           {"unsigned", QJsonObject{{"age", "222"}}},
-                           {"type", "m.room.join_rules"}};
-
-        Event<JoinRulesEventContent> event3;
-        ASSERT_THROW(event3.deserialize(data), DeserializationException);
-}
-
-TEST(RoomEvent, Deserialization)
-{
-        auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
-                                {"event_id", "$asdfafdf8af:matrix.org"},
-                                {"room_id", "!aasdfaeae23r9:matrix.org"},
-                                {"sender", "@alice:matrix.org"},
-                                {"origin_server_ts", 1323238293289323LL},
-                                {"type", "m.room.name"}};
-
-        RoomEvent<NameEventContent> event;
-        event.deserialize(data);
-
-        EXPECT_EQ(event.eventId(), "$asdfafdf8af:matrix.org");
-        EXPECT_EQ(event.roomId(), "!aasdfaeae23r9:matrix.org");
-        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)
-{
-        auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
-                                {"event_id", "$asdfafdf8af:matrix.org"},
-                                {"room_id", "!aasdfaeae23r9:matrix.org"},
-                                {"origin_server_ts", 1323238293289323LL},
-                                {"type", "m.room.name"}};
-
-        RoomEvent<NameEventContent> event;
-
-        try {
-                event.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("sender key is missing", e.what());
-        }
-}
-
-TEST(StateEvent, Deserialization)
-{
-        auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
-                                {"event_id", "$asdfafdf8af:matrix.org"},
-                                {"state_key", "some_state_key"},
-                                {"prev_content", QJsonObject{{"name", "Previous Name"}}},
-                                {"room_id", "!aasdfaeae23r9:matrix.org"},
-                                {"sender", "@alice:matrix.org"},
-                                {"origin_server_ts", 1323238293289323LL},
-                                {"type", "m.room.name"}};
-
-        StateEvent<NameEventContent> event;
-        event.deserialize(data);
-
-        EXPECT_EQ(event.eventId(), "$asdfafdf8af:matrix.org");
-        EXPECT_EQ(event.roomId(), "!aasdfaeae23r9:matrix.org");
-        EXPECT_EQ(event.sender(), "@alice:matrix.org");
-        EXPECT_EQ(event.timestamp(), 1323238293289323);
-        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)
-{
-        auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
-                                {"event_id", "$asdfafdf8af:matrix.org"},
-                                {"prev_content", QJsonObject{{"name", "Previous Name"}}},
-                                {"room_id", "!aasdfaeae23r9:matrix.org"},
-                                {"sender", "@alice:matrix.org"},
-                                {"origin_server_ts", 1323238293289323LL},
-                                {"type", "m.room.name"}};
-
-        StateEvent<NameEventContent> event;
-
-        try {
-                event.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("state_key key is missing", e.what());
-        }
-}
-
-TEST(EventType, Mapping)
-{
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.aliases"}}),
-                  EventType::RoomAliases);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.avatar"}}), EventType::RoomAvatar);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.canonical_alias"}}),
-                  EventType::RoomCanonicalAlias);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.create"}}), EventType::RoomCreate);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.history_visibility"}}),
-                  EventType::RoomHistoryVisibility);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.join_rules"}}),
-                  EventType::RoomJoinRules);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.member"}}), EventType::RoomMember);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.message"}}),
-                  EventType::RoomMessage);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.name"}}), EventType::RoomName);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.power_levels"}}),
-                  EventType::RoomPowerLevels);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.topic"}}), EventType::RoomTopic);
-        EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.unknown"}}),
-                  EventType::Unsupported);
-}
-
-TEST(AliasesEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"aliases", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}};
-
-        AliasesEventContent content;
-        content.deserialize(data);
-
-        EXPECT_EQ(content.aliases().size(), 2);
-        EXPECT_EQ(content.serialize(), data);
-}
-
-TEST(AliasesEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"#test:matrix.org", "#test2:matrix.org"};
-
-        AliasesEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(AliasesEventContent, MissingKey)
-{
-        auto data = QJsonObject{{"key", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}};
-
-        AliasesEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("aliases key is missing", e.what());
-        }
-}
-
-TEST(AvatarEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"url", "https://matrix.org/avatar.png"}};
-
-        AvatarEventContent content;
-        content.deserialize(data);
-
-        EXPECT_EQ(content.url().toString(), "https://matrix.org/avatar.png");
-        EXPECT_EQ(content.serialize(), data);
-}
-
-TEST(AvatarEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"key", "url"};
-
-        AvatarEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(AvatarEventContent, MissingKey)
-{
-        auto data = QJsonObject{{"key", "https://matrix.org"}};
-
-        AvatarEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("url key is missing", e.what());
-        }
-}
-
-TEST(CreateEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"creator", "@alice:matrix.org"}};
-
-        CreateEventContent content;
-        content.deserialize(data);
-
-        EXPECT_EQ(content.creator(), "@alice:matrix.org");
-        EXPECT_EQ(content.serialize(), data);
-}
-
-TEST(CreateEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"creator", "alice"};
-
-        CreateEventContent content;
-
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(CreateEventContent, MissingKey)
-{
-        auto data = QJsonObject{{"key", "@alice:matrix.org"}};
-
-        CreateEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("creator key is missing", e.what());
-        }
-}
-
-TEST(HistoryVisibilityEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"history_visibility", "invited"}};
-
-        HistoryVisibilityEventContent content;
-        content.deserialize(data);
-        EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Invited);
-
-        data = QJsonObject{{"history_visibility", "joined"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Joined);
-
-        data = QJsonObject{{"history_visibility", "shared"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Shared);
-
-        data = QJsonObject{{"history_visibility", "world_readable"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.historyVisibility(), HistoryVisibility::WorldReadable);
-}
-
-TEST(HistoryVisibilityEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"history_visibility", "alice"};
-
-        HistoryVisibilityEventContent content;
-
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(HistoryVisibilityEventContent, InvalidHistoryVisibility)
-{
-        auto data = QJsonObject{{"history_visibility", "wrong"}};
-
-        HistoryVisibilityEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("Unknown history_visibility value: wrong", e.what());
-        }
-}
-
-TEST(HistoryVisibilityEventContent, MissingKey)
-{
-        auto data = QJsonObject{{"key", "joined"}};
-
-        HistoryVisibilityEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("history_visibility key is missing", e.what());
-        }
-}
-
-TEST(JoinRulesEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"join_rule", "invite"}};
-
-        JoinRulesEventContent content;
-        content.deserialize(data);
-        EXPECT_EQ(content.joinRule(), JoinRule::Invite);
-
-        data = QJsonObject{{"join_rule", "knock"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.joinRule(), JoinRule::Knock);
-
-        data = QJsonObject{{"join_rule", "private"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.joinRule(), JoinRule::Private);
-
-        data = QJsonObject{{"join_rule", "public"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.joinRule(), JoinRule::Public);
-}
-
-TEST(JoinRulesEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"rule", "alice"};
-
-        JoinRulesEventContent content;
-
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(JoinRulesEventContent, InvalidHistoryVisibility)
-{
-        auto data = QJsonObject{{"join_rule", "wrong"}};
-
-        JoinRulesEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("Unknown join_rule value: wrong", e.what());
-        }
-}
-
-TEST(JoinRulesEventContent, MissingKey)
-{
-        auto data = QJsonObject{{"key", "invite"}};
-
-        JoinRulesEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("join_rule key is missing", e.what());
-        }
-}
-
-TEST(CanonicalAliasEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"alias", "Room Alias"}};
-
-        CanonicalAliasEventContent content;
-        content.deserialize(data);
-
-        EXPECT_EQ(content.alias(), "Room Alias");
-        EXPECT_EQ(content.serialize(), data);
-}
-
-TEST(CanonicalAliasEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"alias", "Room Alias"};
-
-        CanonicalAliasEventContent content;
-
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(CanonicalAliasEventContent, MissingKey)
-{
-        auto data = QJsonObject{{"key", "alias"}};
-
-        CanonicalAliasEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("alias key is missing", e.what());
-        }
-}
-
-TEST(MemberEventContent, Deserialization)
-{
-        MemberEventContent content;
-
-        auto data = QJsonObject{{"membership", "join"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.membershipState(), Membership::Join);
-
-        data = QJsonObject{{"membership", "invite"}, {"displayname", "Username"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.membershipState(), Membership::Invite);
-        EXPECT_EQ(content.displayName(), "Username");
-
-        data = QJsonObject{{"membership", "leave"}, {"avatar_url", "https://matrix.org"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.membershipState(), Membership::Leave);
-        EXPECT_EQ(content.avatarUrl().toString(), "https://matrix.org");
-
-        data = QJsonObject{{"membership", "ban"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.membershipState(), Membership::Ban);
-
-        data = QJsonObject{{"membership", "knock"}};
-
-        content.deserialize(data);
-        EXPECT_EQ(content.membershipState(), Membership::Knock);
-}
-
-TEST(MemberEventContent, InvalidMembership)
-{
-        auto data = QJsonObject{{"membership", "wrong"}};
-
-        MemberEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("Unknown membership value: wrong", e.what());
-        }
-}
-
-TEST(MemberEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"name", "join"};
-
-        MemberEventContent content;
-
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(MemberEventContent, MissingName)
-{
-        auto data = QJsonObject{{"key", "random"}};
-
-        MemberEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("membership key is missing", e.what());
-        }
-}
-
-TEST(NameEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"name", "Room Name"}};
-
-        NameEventContent content;
-        content.deserialize(data);
-
-        EXPECT_EQ(content.name(), "Room Name");
-        EXPECT_EQ(content.serialize(), data);
-}
-
-TEST(NameEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"name", "Room Name"};
-
-        NameEventContent content;
-
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(NameEventContent, MissingName)
-{
-        auto data = QJsonObject{{"key", "Room Name"}};
-
-        NameEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("name key is missing", e.what());
-        }
-}
-
-TEST(PowerLevelsEventContent, DefaultValues)
-{
-        PowerLevelsEventContent power_levels;
-
-        EXPECT_EQ(power_levels.banLevel(), static_cast<int>(PowerLevels::Moderator));
-        EXPECT_EQ(power_levels.inviteLevel(), static_cast<int>(PowerLevels::Moderator));
-        EXPECT_EQ(power_levels.kickLevel(), static_cast<int>(PowerLevels::Moderator));
-        EXPECT_EQ(power_levels.redactLevel(), static_cast<int>(PowerLevels::Moderator));
-
-        EXPECT_EQ(power_levels.eventsDefaultLevel(), static_cast<int>(PowerLevels::User));
-        EXPECT_EQ(power_levels.usersDefaultLevel(), static_cast<int>(PowerLevels::User));
-        EXPECT_EQ(power_levels.stateDefaultLevel(), static_cast<int>(PowerLevels::Moderator));
-
-        // Default levels.
-        EXPECT_EQ(power_levels.userLevel("@joe:matrix.org"), static_cast<int>(PowerLevels::User));
-        EXPECT_EQ(power_levels.eventLevel("m.room.message"), static_cast<int>(PowerLevels::User));
-}
-
-TEST(PowerLevelsEventContent, FullDeserialization)
-{
-        auto data = QJsonObject{
-          {"ban", 1},
-          {"invite", 2},
-          {"kick", 3},
-          {"redact", 4},
-
-          {"events_default", 5},
-          {"state_default", 6},
-          {"users_default", 7},
-
-          {"events", QJsonObject{{"m.message.text", 8}, {"m.message.image", 9}}},
-          {"users", QJsonObject{{"@alice:matrix.org", 10}, {"@bob:matrix.org", 11}}},
-        };
-
-        PowerLevelsEventContent power_levels;
-        power_levels.deserialize(data);
-
-        EXPECT_EQ(power_levels.banLevel(), 1);
-        EXPECT_EQ(power_levels.inviteLevel(), 2);
-        EXPECT_EQ(power_levels.kickLevel(), 3);
-        EXPECT_EQ(power_levels.redactLevel(), 4);
-
-        EXPECT_EQ(power_levels.eventsDefaultLevel(), 5);
-        EXPECT_EQ(power_levels.stateDefaultLevel(), 6);
-        EXPECT_EQ(power_levels.usersDefaultLevel(), 7);
-
-        EXPECT_EQ(power_levels.userLevel("@alice:matrix.org"), 10);
-        EXPECT_EQ(power_levels.userLevel("@bob:matrix.org"), 11);
-        EXPECT_EQ(power_levels.userLevel("@carl:matrix.org"), 7);
-
-        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)
-{
-        auto data = QJsonObject{
-          {"ban", 1},
-          {"invite", 2},
-
-          {"events_default", 5},
-          {"users_default", 7},
-
-          {"users", QJsonObject{{"@alice:matrix.org", 10}, {"@bob:matrix.org", 11}}},
-        };
-
-        PowerLevelsEventContent power_levels;
-        power_levels.deserialize(data);
-
-        EXPECT_EQ(power_levels.banLevel(), 1);
-        EXPECT_EQ(power_levels.inviteLevel(), 2);
-        EXPECT_EQ(power_levels.kickLevel(), static_cast<int>(PowerLevels::Moderator));
-        EXPECT_EQ(power_levels.redactLevel(), static_cast<int>(PowerLevels::Moderator));
-
-        EXPECT_EQ(power_levels.eventsDefaultLevel(), 5);
-        EXPECT_EQ(power_levels.stateDefaultLevel(), static_cast<int>(PowerLevels::Moderator));
-        EXPECT_EQ(power_levels.usersDefaultLevel(), 7);
-
-        EXPECT_EQ(power_levels.userLevel("@alice:matrix.org"), 10);
-        EXPECT_EQ(power_levels.userLevel("@bob:matrix.org"), 11);
-        EXPECT_EQ(power_levels.userLevel("@carl:matrix.org"), 7);
-
-        EXPECT_EQ(power_levels.eventLevel("m.message.text"), 5);
-        EXPECT_EQ(power_levels.eventLevel("m.message.image"), 5);
-        EXPECT_EQ(power_levels.eventLevel("m.message.gif"), 5);
-}
-
-TEST(PowerLevelsEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"test", "test2"};
-
-        PowerLevelsEventContent power_levels;
-
-        ASSERT_THROW(power_levels.deserialize(data), DeserializationException);
-}
-
-TEST(TopicEventContent, Deserialization)
-{
-        auto data = QJsonObject{{"topic", "Room Topic"}};
-
-        TopicEventContent content;
-        content.deserialize(data);
-
-        EXPECT_EQ(content.topic(), "Room Topic");
-        EXPECT_EQ(content.serialize(), data);
-}
-
-TEST(TopicEventContent, NotAnObject)
-{
-        auto data = QJsonArray{"topic", "Room Topic"};
-
-        TopicEventContent content;
-
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-}
-
-TEST(TopicEventContent, MissingName)
-{
-        auto data = QJsonObject{{"key", "Room Name"}};
-
-        TopicEventContent content;
-        ASSERT_THROW(content.deserialize(data), DeserializationException);
-
-        try {
-                content.deserialize(data);
-        } catch (const DeserializationException &e) {
-                ASSERT_STREQ("topic key is missing", e.what());
-        }
-}
diff --git a/tests/message_events.cc b/tests/message_events.cc
deleted file mode 100644
index dfff50ab..00000000
--- a/tests/message_events.cc
+++ /dev/null
@@ -1,287 +0,0 @@
-#include <gtest/gtest.h>
-
-#include <QJsonArray>
-#include <QJsonObject>
-
-#include "MessageEvent.h"
-#include "MessageEventContent.h"
-
-#include "Audio.h"
-#include "Emote.h"
-#include "File.h"
-#include "Image.h"
-#include "Location.h"
-#include "Notice.h"
-#include "Text.h"
-#include "Video.h"
-
-using namespace matrix::events;
-
-TEST(MessageEvent, Audio)
-{
-        auto info =
-          QJsonObject{ { "duration", 2140786 }, { "mimetype", "audio/mpeg" }, { "size", 1563688 } };
-
-        auto content = QJsonObject{ { "body", "Bee Gees - Stayin' Alive" },
-                                    { "msgtype", "m.audio" },
-                                    { "url", "mxc://localhost/2sdfj23f33r3faad" },
-                                    { "info", info } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::Audio> audio;
-        audio.deserialize(event);
-
-        EXPECT_EQ(audio.msgContent().info().duration, 2140786);
-        EXPECT_EQ(audio.msgContent().info().size, 1563688);
-        EXPECT_EQ(audio.msgContent().info().mimetype, "audio/mpeg");
-        EXPECT_EQ(audio.content().body(), "Bee Gees - Stayin' Alive");
-}
-
-TEST(MessageEvent, Emote)
-{
-        auto content = QJsonObject{ { "body", "emote message" }, { "msgtype", "m.emote" } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::Emote> emote;
-        emote.deserialize(event);
-
-        EXPECT_EQ(emote.content().body(), "emote message");
-}
-
-TEST(MessageEvent, File)
-{
-        auto thumbnail_info = QJsonObject{
-                { "h", 300 }, { "w", 400 }, { "size", 3432434 }, { "mimetype", "image/jpeg" }
-        };
-
-        auto file_info = QJsonObject{ { "size", 24242424 },
-                                      { "mimetype", "application/msword" },
-                                      { "thumbnail_url", "mxc://localhost/adfaefaFAFSDFF3" },
-                                      { "thumbnail_info", thumbnail_info } };
-
-        auto content = QJsonObject{ { "body", "something-important.doc" },
-                                    { "filename", "something-important.doc" },
-                                    { "url", "mxc://localhost/23d233d32r3r2r" },
-                                    { "info", file_info },
-                                    { "msgtype", "m.file" } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::File> file;
-        file.deserialize(event);
-
-        EXPECT_EQ(file.content().body(), "something-important.doc");
-        EXPECT_EQ(file.msgContent().info().thumbnail_info.h, 300);
-        EXPECT_EQ(file.msgContent().info().thumbnail_info.w, 400);
-        EXPECT_EQ(file.msgContent().info().thumbnail_info.mimetype, "image/jpeg");
-        EXPECT_EQ(file.msgContent().info().mimetype, "application/msword");
-        EXPECT_EQ(file.msgContent().info().size, 24242424);
-        EXPECT_EQ(file.content().body(), "something-important.doc");
-}
-
-TEST(MessageEvent, Image)
-{
-        auto thumbinfo = QJsonObject{
-                { "h", 11 }, { "w", 22 }, { "size", 212 }, { "mimetype", "img/jpeg" },
-        };
-
-        auto imginfo = QJsonObject{
-                { "h", 110 },
-                { "w", 220 },
-                { "size", 2120 },
-                { "mimetype", "img/jpeg" },
-                { "thumbnail_url", "https://images.com/image-thumb.jpg" },
-                { "thumbnail_info", thumbinfo },
-        };
-
-        auto content = QJsonObject{ { "body", "Image title" },
-                                    { "msgtype", "m.image" },
-                                    { "url", "https://images.com/image.jpg" },
-                                    { "info", imginfo } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::Image> img;
-        img.deserialize(event);
-
-        EXPECT_EQ(img.content().body(), "Image title");
-        EXPECT_EQ(img.msgContent().info().h, 110);
-        EXPECT_EQ(img.msgContent().info().w, 220);
-        EXPECT_EQ(img.msgContent().info().thumbnail_info.w, 22);
-        EXPECT_EQ(img.msgContent().info().mimetype, "img/jpeg");
-        EXPECT_EQ(img.msgContent().info().thumbnail_url, "https://images.com/image-thumb.jpg");
-}
-
-TEST(MessageEvent, Location)
-{
-        auto thumbnail_info = QJsonObject{
-                { "h", 300 }, { "w", 400 }, { "size", 3432434 }, { "mimetype", "image/jpeg" }
-        };
-
-        auto info = QJsonObject{ { "thumbnail_url", "mxc://localhost/adfaefaFAFSDFF3" },
-                                 { "thumbnail_info", thumbnail_info } };
-
-        auto content = QJsonObject{ { "body", "Big Ben, London, UK" },
-                                    { "geo_uri", "geo:51.5008,0.1247" },
-                                    { "info", info },
-                                    { "msgtype", "m.location" } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::Location> location;
-        location.deserialize(event);
-
-        EXPECT_EQ(location.msgContent().info().thumbnail_info.h, 300);
-        EXPECT_EQ(location.msgContent().info().thumbnail_info.w, 400);
-        EXPECT_EQ(location.msgContent().info().thumbnail_info.mimetype, "image/jpeg");
-        EXPECT_EQ(location.msgContent().info().thumbnail_url, "mxc://localhost/adfaefaFAFSDFF3");
-        EXPECT_EQ(location.content().body(), "Big Ben, London, UK");
-}
-
-TEST(MessageEvent, Notice)
-{
-        auto content = QJsonObject{ { "body", "notice message" }, { "msgtype", "m.notice" } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::Notice> notice;
-        notice.deserialize(event);
-
-        EXPECT_EQ(notice.content().body(), "notice message");
-}
-
-TEST(MessageEvent, Text)
-{
-        auto content = QJsonObject{ { "body", "text message" }, { "msgtype", "m.text" } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::Text> text;
-        text.deserialize(event);
-
-        EXPECT_EQ(text.content().body(), "text message");
-}
-
-TEST(MessageEvent, Video)
-{
-        auto thumbnail_info = QJsonObject{
-                { "h", 300 }, { "w", 400 }, { "size", 3432434 }, { "mimetype", "image/jpeg" }
-        };
-
-        auto video_info = QJsonObject{ { "h", 222 },
-                                       { "w", 333 },
-                                       { "duration", 232323 },
-                                       { "size", 24242424 },
-                                       { "mimetype", "video/mp4" },
-                                       { "thumbnail_url", "mxc://localhost/adfaefaFAFSDFF3" },
-                                       { "thumbnail_info", thumbnail_info } };
-
-        auto content = QJsonObject{ { "body", "Gangnam Style" },
-                                    { "url", "mxc://localhost/23d233d32r3r2r" },
-                                    { "info", video_info },
-                                    { "msgtype", "m.video" } };
-
-        auto event = QJsonObject{ { "content", content },
-                                  { "event_id", "$asdfafdf8af:matrix.org" },
-                                  { "room_id", "!aasdfaeae23r9:matrix.org" },
-                                  { "sender", "@alice:matrix.org" },
-                                  { "origin_server_ts", 1323238293289323LL },
-                                  { "type", "m.room.message" } };
-
-        MessageEvent<messages::Video> video;
-        video.deserialize(event);
-
-        EXPECT_EQ(video.msgContent().info().thumbnail_info.h, 300);
-        EXPECT_EQ(video.msgContent().info().thumbnail_info.w, 400);
-        EXPECT_EQ(video.msgContent().info().thumbnail_info.mimetype, "image/jpeg");
-        EXPECT_EQ(video.msgContent().info().duration, 232323);
-        EXPECT_EQ(video.msgContent().info().size, 24242424);
-        EXPECT_EQ(video.msgContent().info().mimetype, "video/mp4");
-        EXPECT_EQ(video.content().body(), "Gangnam Style");
-}
-
-TEST(MessageEvent, Types)
-{
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.audio" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Audio);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.emote" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Emote);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.file" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::File);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.image" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Image);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.location" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Location);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.notice" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Notice);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.text" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Text);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.video" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Video);
-        EXPECT_EQ(
-          extractMessageEventType(QJsonObject{
-            { "content", QJsonObject{ { "msgtype", "m.random" } } }, { "type", "m.room.message" },
-          }),
-          MessageEventType::Unknown);
-}