diff --git a/discord/guild.py b/discord/guild.py index 2ea56d059..b706c9054 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -41,7 +41,7 @@ from .enums import VoiceRegion, Status, ChannelType, try_enum, VerificationLevel from .mixins import Hashable from .user import User from .invite import Invite -from .iterators import AuditLogIterator +from .iterators import AuditLogIterator, MemberIterator from .webhook import Webhook from .widget import Widget from .asset import Asset @@ -1152,6 +1152,53 @@ class Guild(Hashable): return [convert(d) for d in data] + def fetch_members(self, *, limit=1, after=None): + """|coro| + + Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members. + + .. note:: + + This method is an API call. For general usage, consider :attr:`members` instead. + + .. versionadded:: 1.3.0 + + All parameters are optional. + + Parameters + ---------- + limit: Optional[:class:`int`] + The number of members to retrieve. + Defaults to 1. + after: Optional[Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve members after this date or object. + If a date is provided it must be a timezone-naive datetime representing UTC time. + + Raises + ------ + HTTPException + Getting the members failed. + + Yields + ------ + :class:`.Member` + The member with the member data parsed. + + Examples + -------- + + Usage :: + + async for member in guild.fetch_members(limit=150): + print(member.name) + + Flattening into a list :: + + members = await guild.fetch_members(limit=150).flatten() + # members is now a list of Member... + """ + return MemberIterator(self, limit=limit, after=after) + async def fetch_member(self, member_id): """|coro| diff --git a/discord/http.py b/discord/http.py index 16b657172..17ca74dba 100644 --- a/discord/http.py +++ b/discord/http.py @@ -621,6 +621,16 @@ class HTTPClient: def get_all_guild_channels(self, guild_id): return self.request(Route('GET', '/guilds/{guild_id}/channels', guild_id=guild_id)) + def get_members(self, guild_id, limit, after): + params = { + 'limit': limit, + } + if after: + params['after'] = after + + r = Route('GET', '/guilds/{guild_id}/members', guild_id=guild_id) + return self.request(r, params=params) + def get_member(self, guild_id, member_id): return self.request(Route('GET', '/guilds/{guild_id}/members/{member_id}', guild_id=guild_id, member_id=member_id)) diff --git a/discord/iterators.py b/discord/iterators.py index 17c11fbeb..b2632574a 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -587,3 +587,44 @@ class GuildIterator(_AsyncIterator): self.limit -= retrieve self.after = Object(id=int(data[0]['id'])) return data + +class MemberIterator(_AsyncIterator): + def __init__(self, guild, limit=1, after=None): + + if isinstance(after, datetime.datetime): + after = Object(id=time_snowflake(after, high=True)) + + self.guild = guild + self.limit = limit + self.after = after or OLDEST_OBJECT + + self.state = self.guild._state + self.get_members = self.state.http.get_members + self.members = asyncio.Queue(loop=self.state.loop) + + async def next(self): + if self.members.empty(): + await self.fill_members() + + try: + return self.members.get_nowait() + except asyncio.QueueEmpty: + raise NoMoreItems() + + async def fill_members(self): + if self.limit > 0: + retrieve = self.limit if self.limit <= 1000 else 1000 + + after = self.after.id if self.after else None + data = await self.get_members(self.guild.id, retrieve, after) + + if data: + self.limit -= retrieve + self.after = Object(id=int(data[-1]['user']['id'])) + + for element in reversed(data): + await self.members.put(self.create_member(element)) + + def create_member(self, data): + from .member import Member + return Member(data=data, guild=self.guild, state=self.state)