merge conflict fix

This commit is contained in:
iDutchy 2020-09-15 00:36:19 +00:00
commit 571ddb5a3e
10 changed files with 239 additions and 56 deletions

View File

@ -25,6 +25,7 @@ DEALINGS IN THE SOFTWARE.
"""
import abc
import sys
import copy
import asyncio
@ -169,7 +170,7 @@ class _Overwrites:
self.id = kwargs.pop('id')
self.allow = kwargs.pop('allow', 0)
self.deny = kwargs.pop('deny', 0)
self.type = kwargs.pop('type')
self.type = sys.intern(kwargs.pop('type'))
def _asdict(self):
return {
@ -933,7 +934,6 @@ class Messageable(metaclass=abc.ABCMeta):
"""
return Typing(self)
@utils.deprecated('fetch_message_fast')
async def fetch_message(self, id):
"""|coro|
@ -941,8 +941,6 @@ class Messageable(metaclass=abc.ABCMeta):
This can only be used by bot accounts.
Prefer using :meth:`fetch_message_fast`.
Parameters
------------
id: :class:`int`
@ -967,40 +965,6 @@ class Messageable(metaclass=abc.ABCMeta):
data = await self._state.http.get_message(channel.id, id)
return self._state.create_message(channel=channel, data=data)
async def fetch_message_fast(self, id):
"""|coro|
Retrieves a single :class:`~discord.Message` from the destination, using
the history endpoint.
.. versionadded:: 1.5
Parameters
------------
id: :class:`int`
The message ID to look for.
Raises
--------
~discord.NotFound
The specified channel was not found.
~discord.Forbidden
You do not have permissions to get channel message history.
~discord.HTTPException
The request to get message history failed.
Returns
--------
Optional[:class:`~discord.Message`]
The message asked for, or None if there is no match.
"""
channel = await self._get_channel()
data = await self._state.http.logs_from(channel.id, limit=1, around=id)
if data and int(data[0]['id']) == id:
return self._state.create_message(channel=channel, data=data[0])
return None
async def pins(self):
"""|coro|

View File

@ -142,9 +142,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
@ -565,6 +570,8 @@ class Client:
# sometimes, discord sends us 1000 for unknown reasons so we should reconnect
# regardless and rely on is_closed instead
if isinstance(exc, ConnectionClosed):
if exc.code == 4014:
raise PrivilegedIntentsRequired(exc.shard_id) from None
if exc.code != 1000:
await self.close()
raise

View File

@ -51,7 +51,7 @@ __all__ = (
'Theme',
'WebhookType',
'ExpireBehaviour',
'ExpireBehavior'
'ExpireBehavior',
)
def _create_value_cls(name):

View File

@ -175,3 +175,27 @@ class ConnectionClosed(ClientException):
self.reason = ''
self.shard_id = shard_id
super().__init__('Shard ID %s WebSocket closed with %s' % (self.shard_id, self.code))
class PrivilegedIntentsRequired(ClientException):
"""Exception that's thrown when the gateway is requesting privileged intents
but they're not ticked in the developer page yet.
Go to https://discord.com/developers/applications/ and enable the intents
that are required. Currently these are as follows:
- :attr:`Intents.members`
- :attr:`Intents.presences`
Attributes
-----------
shard_id: Optional[:class:`int`]
The shard ID that got closed if applicable.
"""
def __init__(self, shard_id):
self.shard_id = shard_id
msg = 'Shard ID %s is requesting privileged intents that have not been explicitly enabled in the ' \
'developer portal. It is recommended to go to https://discord.com/developers/applications/ ' \
'and explicitly enable the privileged intents within your application\'s page. If this is not ' \
'possible, then consider disabling the privileged intents instead.'
super().__init__(msg % shard_id)

View File

@ -31,6 +31,7 @@ __all__ = (
'MessageFlags',
'PublicUserFlags',
'Intents',
'MemberCacheFlags',
)
class flag_value:
@ -651,3 +652,127 @@ 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

View File

@ -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)

View File

@ -25,6 +25,7 @@ DEALINGS IN THE SOFTWARE.
"""
import itertools
import sys
from operator import attrgetter
import discord.abc
@ -215,10 +216,10 @@ class Member(discord.abc.Messageable, _BaseUser):
clone = cls(data=data, guild=guild, state=state)
to_return = cls(data=data, guild=guild, state=state)
to_return._client_status = {
key: value
sys.intern(key): sys.intern(value)
for key, value in data.get('client_status', {}).items()
}
to_return._client_status[None] = data['status']
to_return._client_status[None] = sys.intern(data['status'])
return to_return, clone
@classmethod
@ -260,10 +261,10 @@ class Member(discord.abc.Messageable, _BaseUser):
def _presence_update(self, data, user):
self.activities = tuple(map(create_activity, data.get('activities', [])))
self._client_status = {
key: value
sys.intern(key): sys.intern(value)
for key, value in data.get('client_status', {}).items()
}
self._client_status[None] = data['status']
self._client_status[None] = sys.intern(data['status'])
if len(user) > 1:
return self._update_inner_user(user)
@ -285,6 +286,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

View File

@ -34,7 +34,15 @@ from .state import AutoShardedConnectionState
from .client import Client
from .backoff import ExponentialBackoff
from .gateway import *
from .errors import ClientException, InvalidArgument, HTTPException, GatewayNotFound, ConnectionClosed
from .errors import (
ClientException,
InvalidArgument,
HTTPException,
GatewayNotFound,
ConnectionClosed,
PrivilegedIntentsRequired,
)
from . import utils
from .enums import Status
@ -125,6 +133,9 @@ class Shard:
return
if isinstance(e, ConnectionClosed):
if e.code == 4014:
self._queue_put(EventItem(EventType.terminate, self, PrivilegedIntentsRequired(self.id)))
return
if e.code != 1000:
self._queue_put(EventItem(EventType.close, self, e))
return
@ -407,8 +418,11 @@ class AutoShardedClient(Client):
item = await self.__queue.get()
if item.type == EventType.close:
await self.close()
if isinstance(item.error, ConnectionClosed) and item.error.code != 1000:
raise item.error
if isinstance(item.error, ConnectionClosed):
if item.error.code != 1000:
raise item.error
if item.error.code == 4014:
raise PrivilegedIntentsRequired(item.shard.id) from None
return
elif item.type in (EventType.identify, EventType.resume):
await item.shard.reidentify(item.error)

View File

@ -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
@ -110,8 +110,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)
@ -136,6 +134,19 @@ class ConnectionState:
if not intents.members and self._fetch_offline:
raise ValueError('Intents.members has be enabled to fetch offline members.')
else:
intents = Intents()
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
@ -558,6 +569,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.
@ -565,13 +577,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):
@ -691,7 +707,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)
@ -754,7 +770,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)
@ -920,6 +936,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)
@ -930,6 +947,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'])

View File

@ -2803,6 +2803,18 @@ AllowedMentions
.. autoclass:: AllowedMentions
:members:
Intents
~~~~~~~~~~
.. autoclass:: Intents
:members:
MemberCacheFlags
~~~~~~~~~~~~~~~~~~
.. autoclass:: MemberCacheFlags
:members:
File
~~~~~
@ -2912,6 +2924,8 @@ The following exceptions are thrown by the library.
.. autoexception:: ConnectionClosed
.. autoexception:: PrivilegedIntentsRequired
.. autoexception:: discord.opus.OpusError
.. autoexception:: discord.opus.OpusNotLoaded
@ -2928,6 +2942,7 @@ Exception Hierarchy
- :exc:`InvalidArgument`
- :exc:`LoginFailure`
- :exc:`ConnectionClosed`
- :exc:`PrivilegedIntentsRequired`
- :exc:`NoMoreItems`
- :exc:`GatewayNotFound`
- :exc:`HTTPException`