Allow finer grained control over the member cache.
This commit is contained in:
		@@ -141,9 +141,14 @@ class Client:
 | 
			
		||||
    shard_count: Optional[:class:`int`]
 | 
			
		||||
        The total number of shards.
 | 
			
		||||
    intents: :class:`Intents`
 | 
			
		||||
        A list of intents that you want to enable for the session. This is a way of
 | 
			
		||||
        The intents that you want to enable for the session. This is a way of
 | 
			
		||||
        disabling and enabling certain gateway events from triggering and being sent.
 | 
			
		||||
        Currently, if no intents are passed then you will receive all data.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 1.5
 | 
			
		||||
    member_cache_flags: :class:`MemberCacheFlags`
 | 
			
		||||
        Allows for finer control over how the library caches members.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 1.5
 | 
			
		||||
    fetch_offline_members: :class:`bool`
 | 
			
		||||
        Indicates if :func:`.on_ready` should be delayed to fetch all offline
 | 
			
		||||
        members from the guilds the client belongs to. If this is ``False``\, then
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ __all__ = (
 | 
			
		||||
    'Theme',
 | 
			
		||||
    'WebhookType',
 | 
			
		||||
    'ExpireBehaviour',
 | 
			
		||||
    'ExpireBehavior'
 | 
			
		||||
    'ExpireBehavior',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
def _create_value_cls(name):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										126
									
								
								discord/flags.py
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								discord/flags.py
									
									
									
									
									
								
							@@ -31,6 +31,7 @@ __all__ = (
 | 
			
		||||
    'MessageFlags',
 | 
			
		||||
    'PublicUserFlags',
 | 
			
		||||
    'Intents',
 | 
			
		||||
    'MemberCacheFlags',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class flag_value:
 | 
			
		||||
@@ -651,3 +652,128 @@ class Intents(BaseFlags):
 | 
			
		||||
        - :func:`on_typing` (only for DMs)
 | 
			
		||||
        """
 | 
			
		||||
        return 1 << 14
 | 
			
		||||
 | 
			
		||||
@fill_with_flags()
 | 
			
		||||
class MemberCacheFlags(BaseFlags):
 | 
			
		||||
    """Controls the library's cache policy when it comes to members.
 | 
			
		||||
 | 
			
		||||
    This allows for finer grained control over what members are cached.
 | 
			
		||||
    For more information, check :attr:`Client.member_cache_flags`. Note
 | 
			
		||||
    that the bot's own member is always cached.
 | 
			
		||||
 | 
			
		||||
    Due to a quirk in how Discord works, in order to ensure proper cleanup
 | 
			
		||||
    of cache resources it is recommended to have :attr:`Intents.members`
 | 
			
		||||
    enabled. Otherwise the library cannot know when a member leaves a guild and
 | 
			
		||||
    is thus unable to cleanup after itself.
 | 
			
		||||
 | 
			
		||||
    To construct an object you can pass keyword arguments denoting the flags
 | 
			
		||||
    to enable or disable.
 | 
			
		||||
 | 
			
		||||
    The default value is all flags enabled.
 | 
			
		||||
 | 
			
		||||
    .. versionadded:: 1.5
 | 
			
		||||
 | 
			
		||||
    .. container:: operations
 | 
			
		||||
 | 
			
		||||
        .. describe:: x == y
 | 
			
		||||
 | 
			
		||||
            Checks if two flags are equal.
 | 
			
		||||
        .. describe:: x != y
 | 
			
		||||
 | 
			
		||||
            Checks if two flags are not equal.
 | 
			
		||||
        .. describe:: hash(x)
 | 
			
		||||
 | 
			
		||||
               Return the flag's hash.
 | 
			
		||||
        .. describe:: iter(x)
 | 
			
		||||
 | 
			
		||||
               Returns an iterator of ``(name, value)`` pairs. This allows it
 | 
			
		||||
               to be, for example, constructed as a dict or a list of pairs.
 | 
			
		||||
 | 
			
		||||
    Attributes
 | 
			
		||||
    -----------
 | 
			
		||||
    value: :class:`int`
 | 
			
		||||
        The raw value. You should query flags via the properties
 | 
			
		||||
        rather than using this raw value.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __slots__ = ()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        bits = max(self.VALID_FLAGS.values()).bit_length()
 | 
			
		||||
        self.value = (1 << bits) - 1
 | 
			
		||||
        for key, value in kwargs.items():
 | 
			
		||||
            if key not in self.VALID_FLAGS:
 | 
			
		||||
                raise TypeError('%r is not a valid flag name.' % key)
 | 
			
		||||
            setattr(self, key, value)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def all(cls):
 | 
			
		||||
        """A factory method that creates a :class:`MemberCacheFlags` with everything enabled."""
 | 
			
		||||
        bits = max(cls.VALID_FLAGS.values()).bit_length()
 | 
			
		||||
        value = (1 << bits) - 1
 | 
			
		||||
        self = cls.__new__(cls)
 | 
			
		||||
        self.value = value
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def none(cls):
 | 
			
		||||
        """A factory method that creates a :class:`MemberCacheFlags` with everything disabled."""
 | 
			
		||||
        self = cls.__new__(cls)
 | 
			
		||||
        self.value = self.DEFAULT_VALUE
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @flag_value
 | 
			
		||||
    def online(self):
 | 
			
		||||
        """:class:`bool`: Whether to cache members with a status.
 | 
			
		||||
 | 
			
		||||
        For example, members that are part of the initial ``GUILD_CREATE``
 | 
			
		||||
        or become online at a later point. This requires :attr:`Intents.presences`.
 | 
			
		||||
 | 
			
		||||
        Members that go offline are no longer cached.
 | 
			
		||||
        """
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    @flag_value
 | 
			
		||||
    def voice(self):
 | 
			
		||||
        """:class:`bool`: Whether to cache members that are in voice.
 | 
			
		||||
 | 
			
		||||
        This requires :attr:`Intents.voice_states`.
 | 
			
		||||
 | 
			
		||||
        Members that leave voice are no longer cached.
 | 
			
		||||
        """
 | 
			
		||||
        return 2
 | 
			
		||||
 | 
			
		||||
    @flag_value
 | 
			
		||||
    def joined(self):
 | 
			
		||||
        """:class:`bool`: Whether to cache members that joined the guild
 | 
			
		||||
        or are chunked as part of the initial log in flow.
 | 
			
		||||
 | 
			
		||||
        This requires :attr:`Intents.members`.
 | 
			
		||||
 | 
			
		||||
        Members that leave the guild are no longer cached.
 | 
			
		||||
        """
 | 
			
		||||
        return 4
 | 
			
		||||
 | 
			
		||||
    def _verify_intents(self, intents):
 | 
			
		||||
        if self.online and not intents.presences:
 | 
			
		||||
            raise ValueError('MemberCacheFlags.online requires Intents.presences enabled')
 | 
			
		||||
 | 
			
		||||
        if self.voice and not intents.voice_states:
 | 
			
		||||
            raise ValueError('MemberCacheFlags.voice requires Intents.voice_states')
 | 
			
		||||
 | 
			
		||||
        if self.joined and not intents.members:
 | 
			
		||||
            raise ValueError('MemberCacheFlags.joined requires Intents.members')
 | 
			
		||||
 | 
			
		||||
        if not self.joined and self.voice and self.online:
 | 
			
		||||
            msg = 'MemberCacheFlags.voice and MemberCacheFlags.online require MemberCacheFlags.joined ' \
 | 
			
		||||
                  'to properly evict members from the cache.'
 | 
			
		||||
            raise ValueError(msg)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def _voice_only(self):
 | 
			
		||||
        return self.value == 2
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def _online_only(self):
 | 
			
		||||
        return self.value == 1
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -305,11 +305,12 @@ class Guild(Hashable):
 | 
			
		||||
        self._rules_channel_id = utils._get_as_snowflake(guild, 'rules_channel_id')
 | 
			
		||||
        self._public_updates_channel_id = utils._get_as_snowflake(guild, 'public_updates_channel_id')
 | 
			
		||||
 | 
			
		||||
        cache_members = self._state._cache_members
 | 
			
		||||
        cache_online_members = self._state._member_cache_flags.online
 | 
			
		||||
        cache_joined = self._state._member_cache_flags.joined
 | 
			
		||||
        self_id = self._state.self_id
 | 
			
		||||
        for mdata in guild.get('members', []):
 | 
			
		||||
            member = Member(data=mdata, guild=self, state=state)
 | 
			
		||||
            if cache_members or member.id == self_id:
 | 
			
		||||
            if cache_joined or (cache_online_members and member.raw_status != 'offline') or member.id == self_id:
 | 
			
		||||
                self._add_member(member)
 | 
			
		||||
 | 
			
		||||
        self._sync(guild)
 | 
			
		||||
 
 | 
			
		||||
@@ -291,6 +291,14 @@ class Member(discord.abc.Messageable, _BaseUser):
 | 
			
		||||
        """:class:`Status`: The member's overall status. If the value is unknown, then it will be a :class:`str` instead."""
 | 
			
		||||
        return try_enum(Status, self._client_status[None])
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def raw_status(self):
 | 
			
		||||
        """:class:`str`: The member's overall status as a string value.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 1.5
 | 
			
		||||
        """
 | 
			
		||||
        return self._client_status[None]
 | 
			
		||||
 | 
			
		||||
    @status.setter
 | 
			
		||||
    def status(self, value):
 | 
			
		||||
        # internal use only
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ from .member import Member
 | 
			
		||||
from .role import Role
 | 
			
		||||
from .enums import ChannelType, try_enum, Status
 | 
			
		||||
from . import utils
 | 
			
		||||
from .flags import Intents
 | 
			
		||||
from .flags import Intents, MemberCacheFlags
 | 
			
		||||
from .embeds import Embed
 | 
			
		||||
from .object import Object
 | 
			
		||||
from .invite import Invite
 | 
			
		||||
@@ -116,8 +116,6 @@ class ConnectionState:
 | 
			
		||||
            raise TypeError('allowed_mentions parameter must be AllowedMentions')
 | 
			
		||||
 | 
			
		||||
        self.allowed_mentions = allowed_mentions
 | 
			
		||||
        # Only disable cache if both fetch_offline and guild_subscriptions are off.
 | 
			
		||||
        self._cache_members = (self._fetch_offline or self.guild_subscriptions)
 | 
			
		||||
        self._chunk_requests = []
 | 
			
		||||
 | 
			
		||||
        activity = options.get('activity', None)
 | 
			
		||||
@@ -142,6 +140,16 @@ class ConnectionState:
 | 
			
		||||
            if not intents.members and self._fetch_offline:
 | 
			
		||||
                raise ValueError('Intents.members has be enabled to fetch offline members.')
 | 
			
		||||
 | 
			
		||||
        cache_flags = options.get('member_cache_flags', None)
 | 
			
		||||
        if cache_flags is None:
 | 
			
		||||
            cache_flags = MemberCacheFlags.all()
 | 
			
		||||
        else:
 | 
			
		||||
            if not isinstance(cache_flags, MemberCacheFlags):
 | 
			
		||||
                raise TypeError('member_cache_flags parameter must be MemberCacheFlags not %r' % type(cache_flags))
 | 
			
		||||
 | 
			
		||||
        cache_flags._verify_intents(intents)
 | 
			
		||||
 | 
			
		||||
        self._member_cache_flags = cache_flags
 | 
			
		||||
        self._activity = activity
 | 
			
		||||
        self._status = status
 | 
			
		||||
        self._intents = intents
 | 
			
		||||
@@ -564,6 +572,7 @@ class ConnectionState:
 | 
			
		||||
        user = data['user']
 | 
			
		||||
        member_id = int(user['id'])
 | 
			
		||||
        member = guild.get_member(member_id)
 | 
			
		||||
        flags = self._member_cache_flags
 | 
			
		||||
        if member is None:
 | 
			
		||||
            if 'username' not in user:
 | 
			
		||||
                # sometimes we receive 'incomplete' member data post-removal.
 | 
			
		||||
@@ -571,6 +580,7 @@ class ConnectionState:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            member, old_member = Member._from_presence_update(guild=guild, data=data, state=self)
 | 
			
		||||
            if flags.online or (flags._online_only and member.raw_status != 'offline'):
 | 
			
		||||
                guild._add_member(member)
 | 
			
		||||
        else:
 | 
			
		||||
            old_member = Member._copy(member)
 | 
			
		||||
@@ -578,6 +588,9 @@ class ConnectionState:
 | 
			
		||||
            if user_update:
 | 
			
		||||
                self.dispatch('user_update', user_update[0], user_update[1])
 | 
			
		||||
 | 
			
		||||
            if flags._online_only and member.raw_status == 'offline':
 | 
			
		||||
                guild._remove_member(member)
 | 
			
		||||
 | 
			
		||||
        self.dispatch('member_update', old_member, member)
 | 
			
		||||
 | 
			
		||||
    def parse_user_update(self, data):
 | 
			
		||||
@@ -697,7 +710,7 @@ class ConnectionState:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        member = Member(guild=guild, data=data, state=self)
 | 
			
		||||
        if self._cache_members:
 | 
			
		||||
        if self._member_cache_flags.joined:
 | 
			
		||||
            guild._add_member(member)
 | 
			
		||||
        guild._member_count += 1
 | 
			
		||||
        self.dispatch('member_join', member)
 | 
			
		||||
@@ -760,7 +773,7 @@ class ConnectionState:
 | 
			
		||||
        return self._add_guild_from_data(data)
 | 
			
		||||
 | 
			
		||||
    async def chunk_guild(self, guild, *, wait=True, cache=None):
 | 
			
		||||
        cache = cache or self._cache_members
 | 
			
		||||
        cache = cache or self._member_cache_flags.joined
 | 
			
		||||
        future = self.loop.create_future()
 | 
			
		||||
        request = ChunkRequest(guild.id, future, self._get_guild, cache=cache)
 | 
			
		||||
        self._chunk_requests.append(request)
 | 
			
		||||
@@ -926,6 +939,7 @@ class ConnectionState:
 | 
			
		||||
    def parse_voice_state_update(self, data):
 | 
			
		||||
        guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id'))
 | 
			
		||||
        channel_id = utils._get_as_snowflake(data, 'channel_id')
 | 
			
		||||
        flags = self._member_cache_flags
 | 
			
		||||
        if guild is not None:
 | 
			
		||||
            if int(data['user_id']) == self.user.id:
 | 
			
		||||
                voice = self._get_voice_client(guild.id)
 | 
			
		||||
@@ -935,6 +949,13 @@ class ConnectionState:
 | 
			
		||||
 | 
			
		||||
            member, before, after = guild._update_voice_state(data, channel_id)
 | 
			
		||||
            if member is not None:
 | 
			
		||||
                if flags.voice:
 | 
			
		||||
                    if channel_id is None and flags.value == MemberCacheFlags.voice.flag:
 | 
			
		||||
                        # Only remove from cache iff we only have the voice flag enabled
 | 
			
		||||
                        guild._remove_member(member)
 | 
			
		||||
                    else:
 | 
			
		||||
                        guild._add_member(member)
 | 
			
		||||
 | 
			
		||||
                self.dispatch('voice_state_update', member, before, after)
 | 
			
		||||
            else:
 | 
			
		||||
                log.debug('VOICE_STATE_UPDATE referencing an unknown member ID: %s. Discarding.', data['user_id'])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								docs/api.rst
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								docs/api.rst
									
									
									
									
									
								
							@@ -2816,6 +2816,18 @@ AllowedMentions
 | 
			
		||||
.. autoclass:: AllowedMentions
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
Intents
 | 
			
		||||
~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. autoclass:: Intents
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
MemberCacheFlags
 | 
			
		||||
~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. autoclass:: MemberCacheFlags
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
File
 | 
			
		||||
~~~~~
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user