Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ These changes are available on the `master` branch, but have not yet been releas
- Added `Attachment.read_chunked` and added optional `chunksize` argument to
`Attachment.save` for retrieving attachments in chunks.
([#2956](https://github.com/Pycord-Development/pycord/pull/2956))
- Added `Guild.fetch_roles_member_counts` method and `GuildRoleCounts` class.
([#3020](https://github.com/Pycord-Development/pycord/pull/3020))

### Changed

Expand Down
120 changes: 119 additions & 1 deletion discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@
Optional,
Sequence,
Tuple,
TypeVar,
Union,
overload,
)

from typing_extensions import override

from . import abc, utils
from .asset import Asset
from .automod import AutoModAction, AutoModRule, AutoModTriggerMetadata
Expand Down Expand Up @@ -94,7 +97,7 @@
from .welcome_screen import WelcomeScreen, WelcomeScreenChannel
from .widget import Widget

__all__ = ("BanEntry", "Guild")
__all__ = ("BanEntry", "Guild", "GuildRoleCounts")

MISSING = utils.MISSING

Expand Down Expand Up @@ -132,6 +135,8 @@
]
ByCategoryItem = Tuple[Optional[CategoryChannel], List[GuildChannel]]

T = TypeVar("T")


class BanEntry(NamedTuple):
reason: str | None
Expand All @@ -146,6 +151,81 @@ class _GuildLimit(NamedTuple):
filesize: int


class GuildRoleCounts(dict[int, int]):
"""A dictionary subclass that maps role IDs to their member counts.

This class allows accessing member counts by either role ID (:class:`int`) or by
a Snowflake object (which has an ``.id`` attribute).

.. versionadded:: 2.7
"""

@override
def __repr__(self):
return f"<GuildRoleCounts {super().__repr__()}>"

@override
def __getitem__(self, key: int | abc.Snowflake) -> int:
"""Get the member count for a role.

Parameters
----------
key: Union[:class:`int`, :class:`~discord.abc.Snowflake`]
The role ID or a Snowflake object (e.g., a :class:`Role`).

Returns
-------
:class:`int`
The member count for the role.

Raises
------
KeyError
The role ID was not found.
"""
if isinstance(key, abc.Snowflake):
key = key.id
return super().__getitem__(key)

@override
def get(self, key: int | abc.Snowflake, default: T = None) -> int | T:
"""Get the member count for a role, returning a default if not found.

Parameters
----------
key: Union[:class:`int`, :class:`~discord.abc.Snowflake`]
The role ID or a Snowflake object (e.g., a :class:`Role`).
default: Any
The value to return if the role ID is not found.

Returns
-------
Optional[:class:`int`]
The member count for the role, or ``default`` if the role is not present.
"""
if isinstance(key, abc.Snowflake):
key = key.id
return super().get(key, default)

@override
def __contains__(self, key: int | abc.Snowflake) -> bool:
"""Check if a role ID or Snowflake object is in the counts.

Parameters
----------
key: Union[:class:`int`, :class:`~discord.abc.Snowflake`]
The role ID or a Snowflake object (e.g., a :class:`Role`).

Returns
-------
:class:`bool`
``True`` if the role ID is present, ``False`` otherwise.
"""
if isinstance(key, abc.Snowflake):
key = key.id
return super().__contains__(key)


class Guild(Hashable):
"""Represents a Discord guild.

Expand Down Expand Up @@ -1119,6 +1199,44 @@ def get_role(self, role_id: int, /) -> Role | None:
"""
return self._roles.get(role_id)

async def fetch_roles_member_counts(self) -> GuildRoleCounts:
"""|coro|
Fetches a mapping of role IDs to their member counts for this guild.

.. versionadded:: 2.7

Returns
-------
:class:`GuildRoleCounts`
A mapping of role IDs to their member counts. Can be accessed
with either role IDs (:class:`int`) or Snowflake objects (e.g., :class:`Role`).

Raises
------
:exc:`HTTPException`
Fetching the role member counts failed.

Examples
--------

Getting member counts using role IDs:

.. code-block:: python3

counts = await guild.fetch_roles_member_counts()
member_count = counts[123456789]

Using a role object:

.. code-block:: python3

counts = await guild.fetch_roles_member_counts()
role = guild.get_role(123456789)
member_count = counts[role]
"""
r = await self._state.http.get_roles_member_counts(self.id)
return GuildRoleCounts({int(role_id): count for role_id, count in r.items()})

@property
def default_role(self) -> Role:
"""Gets the @everyone role that all members have by default."""
Expand Down
5 changes: 5 additions & 0 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,11 @@ def delete_invite(
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_roles_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 get_role(self, guild_id: Snowflake, role_id: Snowflake) -> Response[role.Role]:
return self.request(
Route(
Expand Down
5 changes: 5 additions & 0 deletions docs/api/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ Role
.. autoclass:: RoleColours
:members:

.. attributetable:: GuildRoleCounts

.. autoclass:: GuildRoleCounts()
:members:

Scheduled Event
~~~~~~~~~~~~~~~

Expand Down