diff --git a/discord/raw_models.py b/discord/raw_models.py index 84f849625..d61b45475 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -42,6 +42,7 @@ if TYPE_CHECKING: IntegrationDeleteEvent, ThreadDeleteEvent, TypingStartEvent, + GuildMemberRemoveEvent, ) from .message import Message from .partial_emoji import PartialEmoji @@ -62,6 +63,7 @@ __all__ = ( 'RawIntegrationDeleteEvent', 'RawThreadDeleteEvent', 'RawTypingEvent', + 'RawMemberRemoveEvent', ) @@ -348,3 +350,23 @@ class RawTypingEvent(_RawReprMixin): self.user: Optional[Union[User, Member]] = None self.timestamp: datetime.datetime = datetime.datetime.fromtimestamp(data['timestamp'], tz=datetime.timezone.utc) self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id') + + +class RawMemberRemoveEvent(_RawReprMixin): + """Represents the payload for a :func:`on_raw_member_remove` event. + + .. versionadded:: 2.0 + + Attributes + ---------- + user: Union[:class:`discord.User`, :class:`discord.Member`] + The user that left the guild. + guild_id: :class:`int` + The ID of the guild the user left. + """ + + __slots__ = ('user', 'guild_id') + + def __init__(self, data: GuildMemberRemoveEvent, user: User, /) -> None: + self.user: Union[User, Member] = user + self.guild_id: int = int(data['guild_id']) diff --git a/discord/state.py b/discord/state.py index 6db292c31..c9e21658f 100644 --- a/discord/state.py +++ b/discord/state.py @@ -991,19 +991,24 @@ class ConnectionState: self.dispatch('member_join', member) def parse_guild_member_remove(self, data: gw.GuildMemberRemoveEvent) -> None: - guild = self._get_guild(int(data['guild_id'])) + user = self.store_user(data['user']) + raw = RawMemberRemoveEvent(data, user) + + guild = self._get_guild(raw.guild_id) if guild is not None: if guild._member_count is not None: guild._member_count -= 1 - user_id = int(data['user']['id']) - member = guild.get_member(user_id) + member = guild.get_member(user.id) if member is not None: + raw.user = member guild._remove_member(member) self.dispatch('member_remove', member) else: _log.debug('GUILD_MEMBER_REMOVE referencing an unknown guild ID: %s. Discarding.', data['guild_id']) + self.dispatch('raw_member_remove', raw) + def parse_guild_member_update(self, data: gw.GuildMemberUpdateEvent) -> None: guild = self._get_guild(int(data['guild_id'])) user = data['user'] diff --git a/docs/api.rst b/docs/api.rst index 3d9940f06..d92ed6b0c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -665,15 +665,40 @@ Members ~~~~~~~~ .. function:: on_member_join(member) - on_member_remove(member) - Called when a :class:`Member` join or leaves a :class:`Guild`. + Called when a :class:`Member` joins a :class:`Guild`. This requires :attr:`Intents.members` to be enabled. - :param member: The member who joined or left. + :param member: The member who joined. :type member: :class:`Member` +.. function:: on_member_remove(member) + + Called when a :class:`Member` leaves a :class:`Guild`. + + If the guild or member could not be found in the internal cache this event + will not be called, you may use :func:`on_raw_member_remove` instead. + + This requires :attr:`Intents.members` to be enabled. + + :param member: The member who left. + :type member: :class:`Member` + +.. function:: on_raw_member_remove(payload) + + Called when a :class:`Member` leaves a :class:`Guild`. + + Unlike :func:`on_member_remove` + this is called regardless of the guild or member being in the internal cache. + + This requires :attr:`Intents.members` to be enabled. + + .. versionadded:: 2.0 + + :param payload: The raw event payload data. + :type payload: :class:`RawMemberRemoveEvent` + .. function:: on_member_update(before, after) Called when a :class:`Member` updates their profile. @@ -4042,6 +4067,14 @@ RawTypingEvent .. autoclass:: RawTypingEvent() :members: +RawMemberRemoveEvent +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: RawMemberRemoveEvent + +.. autoclass:: RawMemberRemoveEvent() + :members: + PartialWebhookGuild ~~~~~~~~~~~~~~~~~~~~