From c65bb4e988ab3e87051c886cd769485aff2a2957 Mon Sep 17 00:00:00 2001 From: shiftinv Date: Thu, 16 Oct 2025 18:10:12 +0200 Subject: [PATCH 1/4] feat(guild): implement `Guild.fetch_role_member_counts` --- disnake/guild.py | 24 ++++++++++++++++++++++++ disnake/http.py | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/disnake/guild.py b/disnake/guild.py index cabf40fe75..4ab03db848 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -3821,6 +3821,30 @@ async def fetch_roles(self) -> List[Role]: data = await self._state.http.get_roles(self.id) return [Role(guild=self, state=self._state, data=d) for d in data] + async def fetch_role_member_counts(self) -> Dict[int, int]: + """|coro| + + Retrieves the member counts of all :class:`Role`\\s that the guild has. + + .. note:: + + This method is an API call. For general usage, consider :attr:`roles` instead. + + .. versionadded:: |vnext| + + Raises + ------ + HTTPException + Retrieving the roles failed. + + Returns + ------- + :class:`dict`\\[:class:`int`, :class:`int`] + The member counts of the roles. + """ + data = await self._state.http.get_role_member_counts(self.id) + return {int(id): count for id, count in data.items()} + @overload async def get_or_fetch_member( self, member_id: int, *, strict: Literal[False] = ... diff --git a/disnake/http.py b/disnake/http.py index c32cbc8842..b98cf0f69a 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -2017,6 +2017,11 @@ def get_role(self, guild_id: Snowflake, role_id: Snowflake) -> Response[role.Rol def get_roles(self, guild_id: Snowflake) -> Response[List[role.Role]]: return self.request(Route("GET", "/guilds/{guild_id}/roles", guild_id=guild_id)) + def get_role_member_counts(self, guild_id: Snowflake) -> Response[Dict[str, int]]: + return self.request( + Route("GET", "/guilds/{guild_id}/roles/member-counts", guild_id=guild_id) + ) + def edit_role( self, guild_id: Snowflake, From 7cfa5358af9d59cebf7f9aab54aea1b408d754c9 Mon Sep 17 00:00:00 2001 From: shiftinv Date: Thu, 16 Oct 2025 18:19:53 +0200 Subject: [PATCH 2/4] feat: return full `Role` objects if possible, skip everyone role --- disnake/guild.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 4ab03db848..cbda211217 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -3821,7 +3821,7 @@ async def fetch_roles(self) -> List[Role]: data = await self._state.http.get_roles(self.id) return [Role(guild=self, state=self._state, data=d) for d in data] - async def fetch_role_member_counts(self) -> Dict[int, int]: + async def fetch_role_member_counts(self) -> Dict[Union[Role, Object], int]: """|coro| Retrieves the member counts of all :class:`Role`\\s that the guild has. @@ -3839,11 +3839,23 @@ async def fetch_role_member_counts(self) -> Dict[int, int]: Returns ------- - :class:`dict`\\[:class:`int`, :class:`int`] + :class:`dict`\\[:class:`Role` | :class:`Object`, :class:`int`] The member counts of the roles. + Roles that could not be found in the bot's cache are + :class:`Object` with the corresponding ID instead. """ data = await self._state.http.get_role_member_counts(self.id) - return {int(id): count for id, count in data.items()} + counts: Dict[Union[Role, Object], int] = {} + for id_str, count in data.items(): + id = int(id_str) + if id == self.id: + # skip @everyone role, since it's a synthetic role and always has a member count of 0 + continue + + obj = self.get_role(id) or Object(id) + counts[obj] = count + + return counts @overload async def get_or_fetch_member( From b36896bfaf86c1134cd03824fffb871f2ffa44b8 Mon Sep 17 00:00:00 2001 From: shiftinv Date: Thu, 16 Oct 2025 18:20:49 +0200 Subject: [PATCH 3/4] docs: add changelog entry --- changelog/1438.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/1438.feature.rst diff --git a/changelog/1438.feature.rst b/changelog/1438.feature.rst new file mode 100644 index 0000000000..26db9cc72e --- /dev/null +++ b/changelog/1438.feature.rst @@ -0,0 +1 @@ +Add :meth:`Guild.fetch_role_member_counts` to retrieve member counts for each :class:`Role` without requiring members to be cached. From b4313e52fd26539e3eebe288c7e50b006c4db835 Mon Sep 17 00:00:00 2001 From: shiftinv Date: Thu, 16 Oct 2025 18:22:26 +0200 Subject: [PATCH 4/4] chore(docs): fix error description --- disnake/guild.py | 2 +- disnake/http.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index cbda211217..40ece9481f 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -3835,7 +3835,7 @@ async def fetch_role_member_counts(self) -> Dict[Union[Role, Object], int]: Raises ------ HTTPException - Retrieving the roles failed. + Retrieving the role member counts failed. Returns ------- diff --git a/disnake/http.py b/disnake/http.py index b98cf0f69a..4513bc6b36 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -2017,7 +2017,7 @@ def get_role(self, guild_id: Snowflake, role_id: Snowflake) -> Response[role.Rol def get_roles(self, guild_id: Snowflake) -> Response[List[role.Role]]: return self.request(Route("GET", "/guilds/{guild_id}/roles", guild_id=guild_id)) - def get_role_member_counts(self, guild_id: Snowflake) -> Response[Dict[str, int]]: + def get_role_member_counts(self, guild_id: Snowflake) -> Response[Dict[Snowflake, int]]: return self.request( Route("GET", "/guilds/{guild_id}/roles/member-counts", guild_id=guild_id) )