From bb33fcb33d090316d7c3d71cb76f1232347d2008 Mon Sep 17 00:00:00 2001
From: Nicolas Werner <nicolas.werner@hotmail.de>
Date: Sat, 10 Apr 2021 23:41:33 +0200
Subject: [PATCH] Add API to set and get account data

---
 include/mtx/events/collections.hpp     | 19 +++++++
 include/mtxclient/http/client.hpp      | 61 ++++++++++++++++++++
 include/mtxclient/http/client_impl.hpp | 78 ++++++++++++++++++++++++++
 lib/http/client.cpp                    | 25 +++++++++
 tests/client_api.cpp                   | 77 +++++++++++++++++++++++++
 5 files changed, 260 insertions(+)

diff --git a/include/mtx/events/collections.hpp b/include/mtx/events/collections.hpp
index 2e8c5fae2..0bbf18331 100644
--- a/include/mtx/events/collections.hpp
+++ b/include/mtx/events/collections.hpp
@@ -316,5 +316,24 @@ constexpr inline EventType to_device_content_to_type<mtx::events::msg::SecretSen
 template<>
 constexpr inline EventType to_device_content_to_type<mtx::events::msg::SecretRequest> =
   EventType::SecretRequest;
+
+//! Get the right event type for some type of account_data event content.
+template<typename Content>
+constexpr inline EventType account_data_content_to_type = EventType::Unsupported;
+
+template<>
+constexpr inline EventType account_data_content_to_type<mtx::events::msc2545::ImagePack> =
+  EventType::ImagePackInAccountData;
+template<>
+constexpr inline EventType account_data_content_to_type<mtx::events::msc2545::ImagePackRooms> =
+  EventType::ImagePackRooms;
+template<>
+constexpr inline EventType account_data_content_to_type<mtx::events::account_data::Tags> =
+  EventType::Tag;
+template<>
+constexpr inline EventType
+  account_data_content_to_type<mtx::events::account_data::nheko_extensions::HiddenEvents> =
+    EventType::NhekoHiddenEvents;
+
 } // namespace events
 } // namespace mtx
diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp
index 89152130d..c6fd3af9f 100644
--- a/include/mtxclient/http/client.hpp
+++ b/include/mtxclient/http/client.hpp
@@ -421,6 +421,42 @@ public:
         void get_event(const std::string &room_id,
                        const std::string &event_id,
                        Callback<mtx::events::collections::TimelineEvents> cb);
+
+        //! Store a room account_data event.
+        template<class Payload>
+        void put_room_account_data(const std::string &room_id,
+                                   const std::string &type,
+                                   const Payload &payload,
+                                   ErrCallback cb);
+        //! Store a room account_data event.
+        template<class Payload>
+        void put_room_account_data(const std::string &room_id,
+                                   const Payload &payload,
+                                   ErrCallback cb);
+
+        //! Store an account_data event.
+        template<class Payload>
+        void put_account_data(const std::string &type, const Payload &payload, ErrCallback cb);
+        //! Store an account_data event.
+        template<class Payload>
+        void put_account_data(const Payload &payload, ErrCallback cb);
+
+        //! Retrieve a room account_data event.
+        template<class Payload>
+        void get_room_account_data(const std::string &room_id,
+                                   const std::string &type,
+                                   Callback<Payload> payload);
+        //! Retrieve a room account_data event.
+        template<class Payload>
+        void get_room_account_data(const std::string &room_id, Callback<Payload> cb);
+
+        //! Retrieve an account_data event.
+        template<class Payload>
+        void get_account_data(const std::string &type, Callback<Payload> payload);
+        //! Retrieve an account_data event.
+        template<class Payload>
+        void get_account_data(Callback<Payload> cb);
+
         //! Send a room message with auto-generated transaction id.
         template<class Payload>
         void send_room_message(const std::string &room_id,
@@ -717,3 +753,28 @@ MTXCLIENT_SEND_TO_DEVICE_FWD(mtx::events::msg::KeyVerificationKey)
 MTXCLIENT_SEND_TO_DEVICE_FWD(mtx::events::msg::KeyVerificationMac)
 MTXCLIENT_SEND_TO_DEVICE_FWD(mtx::events::msg::SecretSend)
 MTXCLIENT_SEND_TO_DEVICE_FWD(mtx::events::msg::SecretRequest)
+
+#define MTXCLIENT_ACCOUNT_DATA_FWD(Payload)                                                        \
+        extern template void mtx::http::Client::put_room_account_data<Payload>(                    \
+          const std::string &room_id,                                                              \
+          const std::string &type,                                                                 \
+          const Payload &payload,                                                                  \
+          ErrCallback cb);                                                                         \
+        extern template void mtx::http::Client::put_room_account_data<Payload>(                    \
+          const std::string &room_id, const Payload &payload, ErrCallback cb);                     \
+        extern template void mtx::http::Client::put_account_data<Payload>(                         \
+          const std::string &type, const Payload &payload, ErrCallback cb);                        \
+        extern template void mtx::http::Client::put_account_data<Payload>(const Payload &payload,  \
+                                                                          ErrCallback cb);         \
+        extern template void mtx::http::Client::get_room_account_data<Payload>(                    \
+          const std::string &room_id, const std::string &type, Callback<Payload> payload);         \
+        extern template void mtx::http::Client::get_room_account_data<Payload>(                    \
+          const std::string &room_id, Callback<Payload> cb);                                       \
+        extern template void mtx::http::Client::get_account_data<Payload>(                         \
+          const std::string &type, Callback<Payload> payload);                                     \
+        extern template void mtx::http::Client::get_account_data<Payload>(Callback<Payload> cb);
+
+MTXCLIENT_ACCOUNT_DATA_FWD(mtx::events::msc2545::ImagePack)
+MTXCLIENT_ACCOUNT_DATA_FWD(mtx::events::msc2545::ImagePackRooms)
+MTXCLIENT_ACCOUNT_DATA_FWD(mtx::events::account_data::nheko_extensions::HiddenEvents)
+MTXCLIENT_ACCOUNT_DATA_FWD(mtx::events::account_data::Tags)
diff --git a/include/mtxclient/http/client_impl.hpp b/include/mtxclient/http/client_impl.hpp
index 54f54c8e3..ff7f99d43 100644
--- a/include/mtxclient/http/client_impl.hpp
+++ b/include/mtxclient/http/client_impl.hpp
@@ -234,3 +234,81 @@ mtx::http::Client::send_state_event(const std::string &room_id,
 {
         send_state_event<Payload>(room_id, "", payload, callback);
 }
+
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::put_room_account_data(const std::string &room_id,
+                                         const std::string &type,
+                                         const Payload &payload,
+                                         ErrCallback cb)
+{
+        const auto api_path = "/client/r0/user/" +
+                              mtx::client::utils::url_encode(user_id_.to_string()) + "/rooms/" +
+                              mtx::client::utils::url_encode(room_id) + "/account_data/" + type;
+        put<Payload>(api_path, payload, cb);
+}
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::put_room_account_data(const std::string &room_id,
+                                         const Payload &payload,
+                                         ErrCallback cb)
+{
+        constexpr auto event_type = mtx::events::account_data_content_to_type<Payload>;
+        static_assert(event_type != mtx::events::EventType::Unsupported);
+        put_room_account_data(room_id, to_string(event_type), payload, std::move(cb));
+}
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::put_account_data(const std::string &type, const Payload &payload, ErrCallback cb)
+{
+        const auto api_path = "/client/r0/user/" +
+                              mtx::client::utils::url_encode(user_id_.to_string()) +
+                              "/account_data/" + type;
+        put<Payload>(api_path, payload, cb);
+}
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::put_account_data(const Payload &payload, ErrCallback cb)
+{
+        constexpr auto event_type = mtx::events::account_data_content_to_type<Payload>;
+        static_assert(event_type != mtx::events::EventType::Unsupported);
+        put_account_data(to_string(event_type), payload, std::move(cb));
+}
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::get_room_account_data(const std::string &room_id,
+                                         const std::string &type,
+                                         Callback<Payload> cb)
+{
+        const auto api_path = "/client/r0/user/" +
+                              mtx::client::utils::url_encode(user_id_.to_string()) + "/rooms/" +
+                              mtx::client::utils::url_encode(room_id) + "/account_data/" + type;
+        get<Payload>(api_path,
+                     [cb](const Payload &res, HeaderFields, RequestErr err) { cb(res, err); });
+}
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::get_room_account_data(const std::string &room_id, Callback<Payload> cb)
+{
+        constexpr auto event_type = mtx::events::account_data_content_to_type<Payload>;
+        static_assert(event_type != mtx::events::EventType::Unsupported);
+        get_room_account_data(room_id, to_string(event_type), std::move(cb));
+}
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::get_account_data(const std::string &type, Callback<Payload> cb)
+{
+        const auto api_path = "/client/r0/user/" +
+                              mtx::client::utils::url_encode(user_id_.to_string()) +
+                              "/account_data/" + type;
+        get<Payload>(api_path,
+                     [cb](const Payload &res, HeaderFields, RequestErr err) { cb(res, err); });
+}
+template<class Payload>
+[[gnu::used, llvm::used]] void
+mtx::http::Client::get_account_data(Callback<Payload> cb)
+{
+        constexpr auto event_type = mtx::events::account_data_content_to_type<Payload>;
+        static_assert(event_type != mtx::events::EventType::Unsupported);
+        get_account_data(to_string(event_type), std::move(cb));
+}
diff --git a/lib/http/client.cpp b/lib/http/client.cpp
index 41709225a..826f41227 100644
--- a/lib/http/client.cpp
+++ b/lib/http/client.cpp
@@ -1493,3 +1493,28 @@ MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationKey)
 MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationMac)
 MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::SecretSend)
 MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::SecretRequest)
+
+#define MTXCLIENT_ACCOUNT_DATA(Payload)                                                            \
+        template void mtx::http::Client::put_room_account_data<Payload>(                           \
+          const std::string &room_id,                                                              \
+          const std::string &type,                                                                 \
+          const Payload &payload,                                                                  \
+          ErrCallback cb);                                                                         \
+        template void mtx::http::Client::put_room_account_data<Payload>(                           \
+          const std::string &room_id, const Payload &payload, ErrCallback cb);                     \
+        template void mtx::http::Client::put_account_data<Payload>(                                \
+          const std::string &type, const Payload &payload, ErrCallback cb);                        \
+        template void mtx::http::Client::put_account_data<Payload>(const Payload &payload,         \
+                                                                   ErrCallback cb);                \
+        template void mtx::http::Client::get_room_account_data<Payload>(                           \
+          const std::string &room_id, const std::string &type, Callback<Payload> payload);         \
+        template void mtx::http::Client::get_room_account_data<Payload>(                           \
+          const std::string &room_id, Callback<Payload> cb);                                       \
+        template void mtx::http::Client::get_account_data<Payload>(const std::string &type,        \
+                                                                   Callback<Payload> payload);     \
+        template void mtx::http::Client::get_account_data<Payload>(Callback<Payload> cb);
+
+MTXCLIENT_ACCOUNT_DATA(mtx::events::msc2545::ImagePack)
+MTXCLIENT_ACCOUNT_DATA(mtx::events::msc2545::ImagePackRooms)
+MTXCLIENT_ACCOUNT_DATA(mtx::events::account_data::nheko_extensions::HiddenEvents)
+MTXCLIENT_ACCOUNT_DATA(mtx::events::account_data::Tags)
diff --git a/tests/client_api.cpp b/tests/client_api.cpp
index b6f153efd..348ed6e86 100644
--- a/tests/client_api.cpp
+++ b/tests/client_api.cpp
@@ -328,6 +328,83 @@ TEST(ClientAPI, TagRoom)
         mtx_client->close();
 }
 
+TEST(ClientAPI, RoomAccountData)
+{
+        std::shared_ptr<Client> mtx_client = make_test_client();
+
+        mtx_client->login(
+          "alice", "secret", [mtx_client](const mtx::responses::Login &, RequestErr err) {
+                  check_error(err);
+          });
+
+        while (mtx_client->access_token().empty())
+                sleep();
+
+        mtx::requests::CreateRoom req;
+        req.name  = "Name";
+        req.topic = "Topic";
+        mtx_client->create_room(
+          req, [mtx_client](const mtx::responses::CreateRoom &res, RequestErr err) {
+                  auto room_id = res.room_id;
+                  check_error(err);
+
+                  mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEv;
+                  hiddenEv.hidden_event_types.push_back(mtx::events::EventType::RoomMember);
+
+                  mtx_client->put_room_account_data(
+                    room_id.to_string(), hiddenEv, [mtx_client, room_id](RequestErr err) {
+                            check_error(err);
+
+                            mtx_client->get_room_account_data<
+                              mtx::events::account_data::nheko_extensions::HiddenEvents>(
+                              room_id.to_string(),
+                              [](mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEv,
+                                 RequestErr err) {
+                                      check_error(err);
+
+                                      ASSERT_EQ(hiddenEv.hidden_event_types.size(), 1);
+                                      EXPECT_EQ(hiddenEv.hidden_event_types.at(0),
+                                                mtx::events::EventType::RoomMember);
+                              });
+                    });
+          });
+
+        mtx_client->close();
+}
+
+TEST(ClientAPI, AccountData)
+{
+        std::shared_ptr<Client> mtx_client = make_test_client();
+
+        mtx_client->login(
+          "alice", "secret", [mtx_client](const mtx::responses::Login &, RequestErr err) {
+                  check_error(err);
+          });
+
+        while (mtx_client->access_token().empty())
+                sleep();
+
+        mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEv;
+        hiddenEv.hidden_event_types.push_back(mtx::events::EventType::RoomMember);
+
+        mtx_client->put_account_data(hiddenEv, [mtx_client](RequestErr err) {
+                check_error(err);
+
+                mtx_client
+                  ->get_account_data<mtx::events::account_data::nheko_extensions::HiddenEvents>(
+                    [mtx_client](mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEv,
+                                 RequestErr err) {
+                            check_error(err);
+
+                            ASSERT_EQ(hiddenEv.hidden_event_types.size(), 1);
+                            EXPECT_EQ(hiddenEv.hidden_event_types.at(0),
+                                      mtx::events::EventType::RoomMember);
+                    });
+        });
+
+        mtx_client->close();
+}
+
 TEST(ClientAPI, LogoutSuccess)
 {
         std::shared_ptr<Client> mtx_client = make_test_client();
-- 
GitLab