diff --git a/hikari/api/rest.py b/hikari/api/rest.py index c50bbbfeb2..e486b6eaa3 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -3606,6 +3606,52 @@ def fetch_members( itself will not raise anything. """ + @abc.abstractmethod + async def search_members( + self, + guild: snowflakes.SnowflakeishOr[guilds.PartialGuild], + name: str, + ) -> typing.Sequence[guilds.Member]: + """Search the members in a guild by nickname and username. + + Parameters + ---------- + guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] + The object or ID of the guild to search members in. + name : str + The query to match username(s) and nickname(s) against. + + Returns + ------- + typing.Sequence[hikari.guilds.Member] + A sequence of the members who matched the provided `name`. + + Raises + ------ + hikari.errors.UnauthorizedError + If you are unauthorized to make the request (invalid/missing token). + hikari.errors.NotFoundError + If the guild is not found. + hikari.errors.RateLimitTooLongError + Raised in the event that a rate limit occurs that is + longer than `max_rate_limit` when making a request. + hikari.errors.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + + !!! note + Unlike `RESTClient.fetch_members` this endpoint isn't paginated and + therefore will return all the members in one go rather than needing + to be asynchronously iterated over. + """ + @abc.abstractmethod async def edit_member( self, diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index dee031b9e2..075f6a273a 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -2000,6 +2000,21 @@ def fetch_members( entity_factory=self._entity_factory, request_call=self._request, guild=guild ) + async def search_members( + self, + guild: snowflakes.SnowflakeishOr[guilds.PartialGuild], + name: str, + ) -> typing.Sequence[guilds.Member]: + route = routes.GET_GUILD_MEMBERS_SEARCH.compile(guild=guild) + query = data_binding.StringMapBuilder() + query.put("query", name) + query.put("limit", 1000) + raw_response = await self._request(route, query=query) + response = typing.cast(data_binding.JSONArray, raw_response) + return data_binding.cast_json_array( + response, self._entity_factory.deserialize_member, guild_id=snowflakes.Snowflake(guild) + ) + async def edit_member( self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild], diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py index 145c0c646e..eb86eaebc6 100644 --- a/hikari/internal/routes.py +++ b/hikari/internal/routes.py @@ -367,6 +367,8 @@ def compile_to_file( GET_GUILD_MEMBERS: typing.Final[Route] = Route(GET, "/guilds/{guild}/members") DELETE_GUILD_MEMBER: typing.Final[Route] = Route(DELETE, "/guilds/{guild}/members/{user}") +GET_GUILD_MEMBERS_SEARCH: typing.Final[Route] = Route(GET, "/guilds/{guild}/members/search") + PUT_GUILD_MEMBER_ROLE: typing.Final[Route] = Route(PUT, "/guilds/{guild}/members/{user}/roles/{role}") DELETE_GUILD_MEMBER_ROLE: typing.Final[Route] = Route(DELETE, "/guilds/{guild}/members/{user}/roles/{role}") diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py index 6b8a3d3738..fa350d4c35 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -2244,6 +2244,17 @@ async def test_fetch_member(self, rest_client): rest_client._request.assert_awaited_once_with(expected_route) rest_client._entity_factory.deserialize_member.assert_called_once_with({"id": "789"}, guild_id=123) + async def test_search_members(self, rest_client): + member = StubModel(645234123) + expected_route = routes.GET_GUILD_MEMBERS_SEARCH.compile(guild=645234123) + expected_query = {"query": "a name", "limit": "1000"} + rest_client._request = mock.AsyncMock(return_value=[{"id": "764435"}]) + rest_client._entity_factory.deserialize_member = mock.Mock(return_value=member) + + assert await rest_client.search_members(StubModel(645234123), "a name") == [member] + rest_client._entity_factory.deserialize_member.assert_called_once_with({"id": "764435"}, guild_id=645234123) + rest_client._request.assert_awaited_once_with(expected_route, query=expected_query) + async def test_edit_member(self, rest_client): expected_route = routes.PATCH_GUILD_MEMBER.compile(guild=123, user=456) expected_json = {"nick": "test", "roles": ["654", "321"], "mute": True, "deaf": False, "channel_id": "987"}