mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-19 15:36:02 +00:00
Allow finer grained control over the member cache.
This commit is contained in:
parent
e6edc44f3d
commit
23ae084b8c
@ -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,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'])
|
||||
|
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
|
||||
~~~~~
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user