feat: Implement guild.fetch_members

This commit is contained in:
Slipstream 2025-06-11 02:06:19 -06:00
parent 28702fa8a1
commit 17b7ea35a9
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
2 changed files with 87 additions and 0 deletions

View File

@ -79,6 +79,8 @@ class GatewayClient:
self._buffer = bytearray()
self._inflator = zlib.decompressobj()
self._member_chunk_requests: Dict[str, asyncio.Future] = {}
async def _reconnect(self) -> None:
"""Attempts to reconnect using exponential backoff with jitter."""
delay = 1.0
@ -237,6 +239,32 @@ class GatewayClient:
}
await self._send_json(payload)
async def request_guild_members(
self,
guild_id: str,
query: str = "",
limit: int = 0,
presences: bool = False,
user_ids: Optional[list[str]] = None,
nonce: Optional[str] = None,
):
"""Sends the request guild members payload to the Gateway."""
payload = {
"op": GatewayOpcode.REQUEST_GUILD_MEMBERS,
"d": {
"guild_id": guild_id,
"query": query,
"limit": limit,
"presences": presences,
},
}
if user_ids:
payload["d"]["user_ids"] = user_ids
if nonce:
payload["d"]["nonce"] = nonce
await self._send_json(payload)
async def _handle_dispatch(self, data: Dict[str, Any]):
"""Handles DISPATCH events (actual Discord events)."""
event_name = data.get("t")
@ -313,6 +341,22 @@ class GatewayClient:
)
await self._dispatcher.dispatch(event_name, raw_event_d_payload)
elif event_name == "GUILD_MEMBERS_CHUNK":
if isinstance(raw_event_d_payload, dict):
nonce = raw_event_d_payload.get("nonce")
if nonce and nonce in self._member_chunk_requests:
future = self._member_chunk_requests[nonce]
if not future.done():
# Append members to a temporary list stored on the future object
if not hasattr(future, "_members"):
future._members = [] # type: ignore
future._members.extend(raw_event_d_payload.get("members", [])) # type: ignore
# If this is the last chunk, resolve the future
if raw_event_d_payload.get("chunk_index") == raw_event_d_payload.get("chunk_count", 1) - 1:
future.set_result(future._members) # type: ignore
del self._member_chunk_requests[nonce]
elif event_name == "INTERACTION_CREATE":
# print(f"GATEWAY RECV INTERACTION_CREATE: {raw_event_d_payload}")
if isinstance(raw_event_d_payload, dict):

View File

@ -1123,6 +1123,49 @@ class Guild:
def __repr__(self) -> str:
return f"<Guild id='{self.id}' name='{self.name}'>"
async def fetch_members(self, *, limit: Optional[int] = None) -> List["Member"]:
"""|coro|
Fetches all members for this guild.
This requires the ``GUILD_MEMBERS`` intent.
Parameters
----------
limit: Optional[int]
The maximum number of members to fetch. If ``None``, all members
are fetched.
Returns
-------
List[Member]
A list of all members in the guild.
Raises
------
DisagreementException
The gateway is not available to make the request.
asyncio.TimeoutError
The request timed out.
"""
if not self._client._gateway:
raise DisagreementException("Gateway not available for member fetching.")
nonce = str(asyncio.get_running_loop().time())
future = self._client._gateway._loop.create_future()
self._client._gateway._member_chunk_requests[nonce] = future
try:
await self._client._gateway.request_guild_members(
self.id, limit=limit or 0, nonce=nonce
)
member_data = await asyncio.wait_for(future, timeout=60.0)
return [Member(m, self._client) for m in member_data]
except asyncio.TimeoutError:
if nonce in self._client._gateway._member_chunk_requests:
del self._client._gateway._member_chunk_requests[nonce]
raise
class Channel:
"""Base class for Discord channels."""