diff --git a/CMakeLists.txt b/CMakeLists.txt index 044975b19b4fdcf313072b046341311cb0614f49..29c34c49bc688f705727d325dc295aa7fae2fe1a 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 e8e3a6fe10fe51c18ddfff3f1ed81384886830e0..fca8e289b1d3c6fd8d8883c727b65af164b5ca36 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 cc19cd87f339dedc6b03f409a63e0102a63e45c0..20ebb783547afa214c6d0196da5daf75c99c08f5 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 7a672185e95a0693fa09b69d52dc8226c71213ad..238293bb01b09dc254728e6afd62309f57aaec6c 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 0000000000000000000000000000000000000000..da38ba778073c3cc6740648d3af64d770ffe75d4 --- /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 2bb3ceca451b6f9d4ddca96de24e98f0969c7b0e..3babd189d30cee03a1a53e5b523e54609bb050bc 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 8e0907d530307cb0aa7778fe6fd268eff4f59bb5..fed026a70fbfcf88c1047625a03f8182a4aab3fe 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 f9847d55385c8cdc5d4dbcff4b24e01c795eddb4..4fc4b258186fe379d5ebec0c9750743e5ddf6a29 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 badc7709f0aaf1fa8877e51c23488c3af469d275..c7133c3c31023e5338fe532c4a51c7d4864340c0 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 0000000000000000000000000000000000000000..fa64aeb13679e226353528914adde812c13cd7e4 --- /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 29feb3008b271fe7028fc278e047f9a116b9475b..4adcd29fb8dd247a8f090dd635f67f24d12967eb 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 c0e43ad0a3655c39a9b71e8967ed8db69b8555b4..6c2a94f7f9d99850db958e38f23cb0d9219dbe16 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 94fecf2bba3aa3eb5a02844afecbf8a1095e4843..17a71ea9dec6a889f81871551c615d64ded665b2 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 d981fcd0578b2474a2580348ea2d9ce110bcae2c..db3feae51e6e75f7f1a7ec0080d6a944368b97e9 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); +}