From 2b4cb8d55686dc81ac4c6b034da52c1e99273f0c Mon Sep 17 00:00:00 2001 From: Malte E <malte.e@mailbox.org> Date: Fri, 11 Nov 2022 14:25:59 +0800 Subject: [PATCH] add user directory search --- CMakeLists.txt | 1 + include/mtx/events_impl.hpp | 6 ++- include/mtx/requests.hpp | 10 +++++ include/mtx/responses.hpp | 1 + include/mtx/responses/users.hpp | 38 +++++++++++++++++ include/mtxclient/crypto/objects.hpp | 18 ++++---- include/mtxclient/http/client.hpp | 6 +++ lib/http/client.cpp | 12 ++++++ lib/structs/requests.cpp | 7 +++ lib/structs/responses/users.cpp | 29 +++++++++++++ meson.build | 1 + tests/client_api.cpp | 64 ++++++++++++++++++++++++++++ tests/requests.cpp | 13 ++++++ tests/responses.cpp | 20 +++++++++ 14 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 include/mtx/responses/users.hpp create mode 100644 lib/structs/responses/users.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 044975b19..29c34c49b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,7 @@ target_sources(matrix_client lib/structs/responses/register.cpp lib/structs/responses/sync.cpp lib/structs/responses/turn_server.cpp + lib/structs/responses/users.cpp lib/structs/responses/version.cpp lib/structs/responses/well-known.cpp lib/structs/responses/public_rooms.cpp) diff --git a/include/mtx/events_impl.hpp b/include/mtx/events_impl.hpp index e8e3a6fe1..fca8e289b 100644 --- a/include/mtx/events_impl.hpp +++ b/include/mtx/events_impl.hpp @@ -10,12 +10,14 @@ namespace detail { template<typename, typename = void> struct can_edit : std::false_type -{}; +{ +}; template<typename Content> struct can_edit<Content, std::void_t<decltype(Content::relations)>> : std::is_same<decltype(Content::relations), mtx::common::Relations> -{}; +{ +}; } template<class Content> diff --git a/include/mtx/requests.hpp b/include/mtx/requests.hpp index cc19cd87f..20ebb7835 100644 --- a/include/mtx/requests.hpp +++ b/include/mtx/requests.hpp @@ -439,5 +439,15 @@ struct SetPusher friend void to_json(nlohmann::json &obj, const SetPusher &req); }; + +struct userDirectorySearch +{ + //! The maximum number of results to return. Defaults to 10. + int limit; + //! Required: The term to search for + std::string search_term; + + friend void to_json(nlohmann::json &obj, const userDirectorySearch &data); +}; } // namespace requests } // namespace mtx diff --git a/include/mtx/responses.hpp b/include/mtx/responses.hpp index 7a672185e..238293bb0 100644 --- a/include/mtx/responses.hpp +++ b/include/mtx/responses.hpp @@ -20,5 +20,6 @@ #include "responses/register.hpp" #include "responses/sync.hpp" #include "responses/turn_server.hpp" +#include "responses/users.hpp" #include "responses/version.hpp" #include "responses/well-known.hpp" diff --git a/include/mtx/responses/users.hpp b/include/mtx/responses/users.hpp new file mode 100644 index 000000000..da38ba778 --- /dev/null +++ b/include/mtx/responses/users.hpp @@ -0,0 +1,38 @@ +#pragma once + +/// @file +/// @brief Response for the endpoint to search users + +#if __has_include(<nlohmann/json_fwd.hpp>) +#include <nlohmann/json_fwd.hpp> +#else +#include <nlohmann/json.hpp> +#endif + +namespace mtx { +namespace responses { + +struct User +{ + //! The avatar url, as an MXC, if one exists. + std::string avatar_url; + //! The display name of the user, if one exists. + std::string display_name; + //! The user’s matrix user ID. + std::string user_id; + + friend void from_json(const nlohmann::json &obj, User &res); +}; +//! User directory search results. +struct Users +{ + //! If the search was limited by the search limit. + bool limited; + + //! A chunk of user events. + std::vector<User> results; + + friend void from_json(const nlohmann::json &obj, Users &res); +}; +} +} diff --git a/include/mtxclient/crypto/objects.hpp b/include/mtxclient/crypto/objects.hpp index 2bb3ceca4..3babd189d 100644 --- a/include/mtxclient/crypto/objects.hpp +++ b/include/mtxclient/crypto/objects.hpp @@ -31,49 +31,49 @@ struct OlmDeleter void operator()(OlmAccount *ptr) { olm_clear_account(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmUtility *ptr) { olm_clear_utility(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmPkDecryption *ptr) { olm_clear_pk_decryption(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmPkEncryption *ptr) { olm_clear_pk_encryption(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmPkSigning *ptr) { olm_clear_pk_signing(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmSession *ptr) { olm_clear_session(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmOutboundGroupSession *ptr) { olm_clear_outbound_group_session(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmInboundGroupSession *ptr) { olm_clear_inbound_group_session(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } void operator()(OlmSAS *ptr) { olm_clear_sas(ptr); - delete[] (reinterpret_cast<uint8_t *>(ptr)); + delete[](reinterpret_cast<uint8_t *>(ptr)); } }; diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp index 8e0907d53..fed026a70 100644 --- a/include/mtxclient/http/client.hpp +++ b/include/mtxclient/http/client.hpp @@ -83,6 +83,7 @@ struct Sync; struct StateEvents; struct TurnServer; struct UploadKeys; +struct Users; struct Version; struct Versions; struct WellKnown; @@ -761,6 +762,11 @@ public: //! Sets, updates, or deletes a pusher void set_pusher(const mtx::requests::SetPusher &req, Callback<mtx::responses::Empty> cb); + //! Searches the user directory + void search_user_directory(const std::string &search_term, + int limit, + Callback<mtx::responses::Users> callback); + private: template<class Request, class Response> void post(const std::string &endpoint, diff --git a/lib/http/client.cpp b/lib/http/client.cpp index f9847d553..4fc4b2581 100644 --- a/lib/http/client.cpp +++ b/lib/http/client.cpp @@ -1660,6 +1660,18 @@ Client::set_pusher(const mtx::requests::SetPusher &req, Callback<mtx::responses: "/client/v3/pushers/set", req, std::move(cb)); } +void +Client::search_user_directory(const std::string &search_term, + int limit, + Callback<mtx::responses::Users> callback) +{ + mtx::requests::userDirectorySearch req; + req.search_term = search_term; + req.limit = limit; + post<mtx::requests::userDirectorySearch, mtx::responses::Users>( + "/client/v3/user_directory/search", req, std::move(callback)); +} + // Template instantiations for the various send functions #define MTXCLIENT_SEND_STATE_EVENT(Content) \ diff --git a/lib/structs/requests.cpp b/lib/structs/requests.cpp index badc7709f..c7133c3c3 100644 --- a/lib/structs/requests.cpp +++ b/lib/structs/requests.cpp @@ -287,5 +287,12 @@ to_json(json &obj, const SetPusher &req) obj["append"] = req.append; } +void +to_json(json &obj, const userDirectorySearch &request) +{ + obj["limit"] = request.limit; + obj["search_term"] = request.search_term; +} + } // namespace requests } // namespace mtx diff --git a/lib/structs/responses/users.cpp b/lib/structs/responses/users.cpp new file mode 100644 index 000000000..fa64aeb13 --- /dev/null +++ b/lib/structs/responses/users.cpp @@ -0,0 +1,29 @@ +#include "mtx/responses/users.hpp" + +#include <nlohmann/json.hpp> + +using json = nlohmann::json; + +namespace mtx { +namespace responses { + +void +from_json(const json &obj, User &user) +{ + if (obj.count("avatar_url") != 0 && !obj.at("avatar_url").is_null()) + user.avatar_url = obj.at("avatar_url").get<std::string>(); + + if (obj.count("display_name") != 0 && !obj.at("display_name").is_null()) + user.display_name = obj.at("display_name").get<std::string>(); + + user.user_id = obj.at("user_id").get<std::string>(); +} + +void +from_json(const json &obj, Users &users) +{ + users.limited = obj.at("limited").get<bool>(); + users.results = obj.at("results").get<std::vector<User>>(); +} +} +} diff --git a/meson.build b/meson.build index 29feb3008..4adcd29fb 100644 --- a/meson.build +++ b/meson.build @@ -122,6 +122,7 @@ src = [ 'lib/structs/responses/register.cpp', 'lib/structs/responses/sync.cpp', 'lib/structs/responses/turn_server.cpp', + 'lib/structs/responses/users.cpp', 'lib/structs/responses/version.cpp', 'lib/structs/responses/well-known.cpp', 'lib/structs/secret_storage.cpp', diff --git a/tests/client_api.cpp b/tests/client_api.cpp index c0e43ad0a..6c2a94f7f 100644 --- a/tests/client_api.cpp +++ b/tests/client_api.cpp @@ -1845,6 +1845,70 @@ TEST(ClientAPI, PublicRooms) bob->close(); } +TEST(ClientAPI, Users) +{ + auto alice = make_test_client(); + auto bob = make_test_client(); + auto carl = make_test_client(); + + alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) { + check_error(err); + }); + + bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) { + check_error(err); + bob->set_displayname("Bob", [](RequestErr err) { check_error(err); }); + }); + + carl->login("carl", "secret", [carl](const mtx::responses::Login &, RequestErr err) { + check_error(err); + carl->set_displayname("Bobby", [](RequestErr err) { check_error(err); }); + }); + + while (alice->access_token().empty() || bob->access_token().empty() || + carl->access_token().empty()) + sleep(); + + mtx::requests::CreateRoom req; + req.name = "Name"; + req.topic = "Topic"; + req.invite = {"@bob:" + server_name(), "@carl:" + server_name()}; + alice->create_room(req, [bob, carl](const mtx::responses::CreateRoom &res, RequestErr err) { + check_error(err); + auto room_id = res.room_id.to_string(); + + bob->join_room(room_id, + [](const mtx::responses::RoomId &, RequestErr err) { check_error(err); }); + + carl->join_room(room_id, + [](const mtx::responses::RoomId &, RequestErr err) { check_error(err); }); + }); + + alice->search_user_directory( + "carl", 10, [alice](const mtx::responses::Users &users, RequestErr err) { + check_error(err); + EXPECT_EQ(users.results.size(), 1); + EXPECT_EQ(users.results[0].display_name, "Bobby"); + EXPECT_EQ(users.limited, false); + }); + alice->search_user_directory( + "Bob", 0, [alice](const mtx::responses::Users &users, RequestErr err) { + check_error(err); + EXPECT_EQ(users.results.size(), 1); + EXPECT_EQ(users.limited, true); + }); + alice->search_user_directory( + "Bob", 10, [alice](const mtx::responses::Users &users, RequestErr err) { + check_error(err); + EXPECT_EQ(users.results.size(), 2); + EXPECT_EQ(users.limited, false); + }); + + alice->close(); + bob->close(); + carl->close(); +} + TEST(ClientAPI, Summary) { // Setup : Create a new (public) room with some settings diff --git a/tests/requests.cpp b/tests/requests.cpp index 94fecf2bb..17a71ea9d 100644 --- a/tests/requests.cpp +++ b/tests/requests.cpp @@ -354,3 +354,16 @@ TEST(Requests, PublicRooms) EXPECT_THROW(json req = b3, std::invalid_argument); } + +TEST(Requests, userDirectorySearch) +{ + userDirectorySearch search; + search.search_term = "foo"; + search.limit = 10; + + json j = search; + EXPECT_EQ(j, R"({ + "limit": 10, + "search_term": "foo" + })"_json); +} diff --git a/tests/responses.cpp b/tests/responses.cpp index d981fcd05..db3feae51 100644 --- a/tests/responses.cpp +++ b/tests/responses.cpp @@ -1608,3 +1608,23 @@ TEST(Responses, PublicRooms) EXPECT_EQ(publicRooms.prev_batch, "p1902"); EXPECT_EQ(publicRooms.total_room_count_estimate, 115); } + +TEST(Response, Users) +{ + json data = R"({ + "limited": false, + "results": [ + { + "avatar_url": "mxc://bar.com/foo", + "display_name": "Foo", + "user_id": "@foo:bar.com" + } + ] + })"_json; + Users users = data.get<Users>(); + EXPECT_EQ(users.results.size(), 1); + EXPECT_EQ(users.results[0].avatar_url, "mxc://bar.com/foo"); + EXPECT_EQ(users.results[0].display_name, "Foo"); + EXPECT_EQ(users.results[0].user_id, "@foo:bar.com"); + EXPECT_EQ(users.limited, false); +} -- GitLab