aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--discord/client.py9
-rw-r--r--discord/enums.py2
-rw-r--r--discord/flags.py126
-rw-r--r--discord/guild.py5
-rw-r--r--discord/member.py8
-rw-r--r--discord/state.py33
-rw-r--r--docs/api.rst12
7 files changed, 184 insertions, 11 deletions
diff --git a/discord/client.py b/discord/client.py
index c39a26da..861617b7 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -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
diff --git a/discord/enums.py b/discord/enums.py
index 7fa39289..e74e6698 100644
--- a/discord/enums.py
+++ b/discord/enums.py
@@ -51,7 +51,7 @@ __all__ = (
'Theme',
'WebhookType',
'ExpireBehaviour',
- 'ExpireBehavior'
+ 'ExpireBehavior',
)
def _create_value_cls(name):
diff --git a/discord/flags.py b/discord/flags.py
index bc2a52ed..a204937c 100644
--- a/discord/flags.py
+++ b/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
+
diff --git a/discord/guild.py b/discord/guild.py
index 2ab4afb2..4247f105 100644
--- a/discord/guild.py
+++ b/discord/guild.py
@@ -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)
diff --git a/discord/member.py b/discord/member.py
index 00f72fb2..4b8fb8a0 100644
--- a/discord/member.py
+++ b/discord/member.py
@@ -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
diff --git a/discord/state.py b/discord/state.py
index ad491197..39190f1e 100644
--- a/discord/state.py
+++ b/discord/state.py
@@ -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,13 +580,17 @@ class ConnectionState:
return
member, old_member = Member._from_presence_update(guild=guild, data=data, state=self)
- guild._add_member(member)
+ if flags.online or (flags._online_only and member.raw_status != 'offline'):
+ guild._add_member(member)
else:
old_member = Member._copy(member)
user_update = member._presence_update(data=data, user=user)
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'])
diff --git a/docs/api.rst b/docs/api.rst
index d4af5ff1..b3c10991 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -2816,6 +2816,18 @@ AllowedMentions
.. autoclass:: AllowedMentions
:members:
+Intents
+~~~~~~~~~~
+
+.. autoclass:: Intents
+ :members:
+
+MemberCacheFlags
+~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: MemberCacheFlags
+ :members:
+
File
~~~~~