Repository: Rapptz/discord.py

Issue:
Fire on member remove event even if the member is not cached
### Summary

discord.py should fire on member remove event if the member member is cached or not cached

### What is the feature request for?

The core library

### The Problem

In order to use some member events like on_member_remove, etc, discord.py implements member cache to fire these events, when the bot sees more users, caching members takes a lot of ram. I think that on_member_remove event should be fired even if the member is not cached . 

### The Ideal Solution

To fire on member remove event if the member is cached or is not cached

### The Current Solution

I use on_socket_raw_receive event to get the raw member remove event, this takes a lot of cpu, I do not really know why it takes a lot of cpu when you have `enable_debug_events=True` in the bot constructor 

### Additional Context

_No response_


Discussion:
Library has to resolve the member from cache on these events because the data sent by Discord is incomplete (partial) and a new member instance cannot be created from that data.
This is planned during the eventual event refactor.

Resolving patch:
diff --git a/discord/raw_models.py b/discord/raw_models.py
--- a/discord/raw_models.py
+++ b/discord/raw_models.py
@@ -42,6 +42,7 @@
         IntegrationDeleteEvent,
         ThreadDeleteEvent,
         TypingStartEvent,
+        GuildMemberRemoveEvent,
     )
     from .message import Message
     from .partial_emoji import PartialEmoji
@@ -62,6 +63,7 @@
     'RawIntegrationDeleteEvent',
     'RawThreadDeleteEvent',
     'RawTypingEvent',
+    'RawMemberRemoveEvent',
 )
 
 
@@ -348,3 +350,23 @@ def __init__(self, data: TypingStartEvent, /) -> None:
         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
--- a/discord/state.py
+++ b/discord/state.py
@@ -991,19 +991,24 @@ def parse_guild_member_add(self, data: gw.GuildMemberAddEvent) -> None:
         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']


Test patch: